3.2.1 线程操作

进程与线程介绍

线程是操作系统能够运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

从技术性上来说,一段上下文(就是一个线程)由若干CPU寄存器的值组成,

进程与线程不同,一条线程是一段指令上下文。一个进程是一堆与计算有关的资源集合。一个进程可以有一条或多条线程。

注意:与进程有关的资源集合包含内存空间(一个进程中的所有线程共享同一块内存空间),文件描述符(例如:打开的socket链接)和安全认证(如:启动进程的用户的ID)。

进程

一个程序的一个执行实例被称为一个进程。

每个进程提供执行一个程序所需的资源。

一个进程有一个虚拟地址空间,可执行的代码,对操作系统对象开放的接口(句柄),安全认证上下文,一个唯一的进程标识符(PID),环境变量,优先级类,最小与最大的指定的工作空间以及最少执行的一条线程。

每一个进程伴随着一条线程启动,通常被称为主线程,主线程可以通过任何其他线程创建额外的线程。

进程与线程的区别

线程共享一个进程的地址(内存)空间,而进程拥有独立的内存空间。

线程可以直接连接到进程的数据段,而进程则拥有独立的父进程的数据段副本。

同一个进程的线程之间可以直接交流;而多个进程则需要一个中间进程来实现通信。

创建一个新线程很容易;而创建新进程则需要对其父进程进行复制。

线程可以对同一进程里的其他线程进行控制;进程则只能对其子进程进行控制。

对主要线程的改动(取消,优先级变动等)会影响进程中的其他线程的运行;而父进程的改动不会影响子进程。

并发多线程效果演示

示例

import threading
import time

def run(n):
    print('task:', n)
    before_sleep = time.time()
    time.sleep(2)
    after_sleep = time.time()
    print(after_sleep - before_sleep)

t1 = threading.Thread(target=run, args=('t1', ))    #args参数为元组,所以必须加逗号
t2 = threading.Thread(target=run, args=('t2', ))
t1.start()
t2.start()

结果

task: t1
task: t2
2.021209478378296
2.021209478378296

看,如果是串行,2个线程应该睡4秒,但这里睡了2秒,成功!

下面是基于类的写法,效果是一样的

import threading
import time

class MyThread(threading.Thread):
    def __init__(self, n):
        super(MyThread, self).__init__()
        self.n = n

    def run(self):
        print('task: ', self.n)
        before_sleep = time.time()
        time.sleep(2)
        after_sleep = time.time()
        print(after_sleep - before_sleep)

t1 = MyThread('t1')
t2 = MyThread('t2')
t1.start()
t2.start()

结果

task:  t1
task:  t2
2.0057871341705322
2.0057871341705322

这种方式不常用,知道就行。

继承式多线程

下面是一个继承式多线程的示例,同时启动50个线程,完毕后统一时间

import threading
import time

def run(n):
    print('task: ', n)
    time.sleep(2)

start_time = time.time()
t_l = []    #存放线程
for i in range(50):
    t = threading.Thread(target=run, args=('t-%s' % i, ))
    t.start()
    t_l.append(t)    #为了不阻塞其他线程的启动,先放在列表里,最后join

for t in t_l:    #循环线程列表,执行所有线程
    t.join()

print('All threads have ran down!')
print('Total cost: ', time.time() - start_time)

结果

...
...
...
task:  t-44
task:  t-45
task:  t-46
task:  t-47
task:  t-48
task:  t-49
All threads have ran down!
Total cost:  2.021458387374878

守护线程

在创建新线程时,子线程会从其父线程继承其线程属性,主线程是普通的非守护线程,默认情况下,它所创建的任何线程都是非守护线程。

在默认情况下,新线程通常会生成非守护线程或普通线程,如果新线程在运行,主线程将永远等待,无法正常退出。

主线程执行完毕并且没有任何非守护线程继续运行时,主线程可以正常终止退出了。

注意:

  • 必须在启动之前将线程配置为守护程序或非守护程序,否则Python将引发运行时错误;
  • 最后守护程序线程不会像普通线程一样正常退出,当程序中的所有非守护程序线程都完成执行时,任何剩余的守护程序线程将在Python退出时被放弃,在设计守护线程时,需要确保在主线程退出时不会产生任何负面影响。

示例

import threading
import time

start_time = time.time()
def run(n):
    print('task: ', n)
    time.sleep(2)
    print('Daemon thread cost: ', time.time() - start_time)

t_l = []    #存放线程
for i in range(10):
    t = threading.Thread(target=run, args=('t-%s' % i, ))
    t.setDaemon(True)    #将当前线程设置为守护线程,daemon希腊神话中的半人半神精灵
    t.start()
    t_l.append(t)    #为了不阻塞其他线程的启动,先放在列表里,最后join

# for t in t_l:    #循环线程列表,执行所有线程
#     t.join()

print('All threads have ran down!')
print('Primary thread cost: ', time.time() - start_time)

2个结果,分别是

t.setDaemon(True)
task:  t-0
task:  t-1
task:  t-2
task:  t-3
task:  t-4
task:  t-5
task:  t-6
task:  t-7
task:  t-8
task: All threads have ran down!
 Primary thread cost:  0.0
t.setDaemon(False)
task:  t-0
task:  t-1
task:  t-2
task:  t-3
task:  t-4
task:  t-5
task:  t-6
task:  t-7
task:  t-8
task: All threads have ran down! 
t-9
Primary thread cost:  0.0
Daemon thread cost: Daemon thread cost:   Daemon thread cost: 2.005704402923584
 2.005704402923584
Daemon thread cost: Daemon thread cost:  2.005704402923584
Daemon thread cost:  2.005704402923584
2.005704402923584
Daemon thread cost:  2.005704402923584
 2.005704402923584
Daemon thread cost: Daemon thread cost: Daemon thread cost:  2.005704402923584
 2.005704402923584 2.005704402923584

可以看到,当子线程设置为守护线程时,主线程并没有理会子线程是否结束(没有执行sleep(2)语句)就已经结束了。当子线程为非守护线程时,主线程等待子线程执行完毕才结束。

猜你喜欢

转载自www.cnblogs.com/infinitecodes/p/12114461.html