多线程编程
1、Python 的多线程支持模块简介
Python 提供了多个模块来支持多线程编程,包括 thread、threading 和 Queue 模块等。程序是可以使用 thread 和 threading 模块来创建与管理线程。thread 模块提供了基本的线程和锁定支持;而 threading 模块提供了更高级别、功能更全面的线程管理。使用 Queue 模块,用户可以创建一支队列数据结构,用于在多线程之间进行共享。
-
核心提示:避免使用 thread 模块
推荐使用更高级别的 threading 模块,而不使用 thread 模块有很多原因:
- threading 模块更加先进,有更好的线程支持,并且 thread 模块中的一些属性会和 threading 模块有冲突。
- 另一个原因是低级别的 thread 模块拥有的同步原语很少(实际只有一个),而 threading 模块则很多。
- 避免使用 thread 模块的另一个原因是它对于进程如何退出没有控制。当主线程结束时,所有其他线程也都强制结束,不会发出警告或者进行适当的清理。但至少 threading 模块能确保重要的子线程在进程退出前结束。
2、thread 模块
除了派生线程外,thread 模块还提供了基本的同步数据结构,称为锁对象(也称为原语锁、简单锁、互斥锁、互斥和二进制信号量)。下表展示了一些常用的线程函数以及 LockType 锁对象的方法:
函数/方法 | 说明 |
---|---|
thread 模块的函数 | |
start_new_thread(function,args,kwargs=None) | 派生一个新的线程,使用给定的 args 和可选的 kwargs 来执行 function |
allocate_lock() | 分配 LockType 锁对象 |
exit() | 给线程退出指令 |
LockType 锁对象的方法 | |
acquire(wait=None) | 尝试获取锁对象 |
locked() | 如果获取了锁对象则返回 True,否则,返回 False |
release() | 释放锁 |
3、threading 模块
除了 Thread 类之外,该模块还包括许多非常好用的同步机制。下表给出了 threading 模块中所有可用对象的列表。
对象 | 说明 |
---|---|
Thread | 表示一个执行线程的对象 |
Lock | 锁原语对象 |
RLock | 可重入锁对象,使单一线程可以(再次)获得已持有的锁(递归锁) |
Condition | 条件变量对象,使得一个线程等待另一个线程满足特定的 “条件”,比如改变状态或某个数据值 |
Event | 条件变量的通用版本,任意数量的线程等待某个事件的发生,在该事件发生后所有线程将被激活 |
Semaphore | 为线程间共享的有限资源提供一个 “计数器”,如果没有可用资源时会阻塞 |
BoundedSemaphore | 与 Semaphore 相似,不过它不允许超过初始值 |
Timer | 与 Thread 相似,不过它要在运行前等待一段时间 |
Barrier | 创建一个 “障碍”,必须达到指定数量的线程后才可以继续 |
-
核心提示:守护线程
避免使用 thread 的另一个原因是该模块不支持守护线程的概念。**当主线程退出时,所有子线程都将终止,不管他们是否正在工作。**如果你不想发生这种行为,就要引入守护线程了。
threading 模块支持守护线程,其工作方式是:守护线程一般是一个等待客户端请求的服务器。如果没有客户端请求,守护线程就是空闲的。如果把一个线程设置成守护线程,就表示这个线程是不重要的,进程退出时不需要等待这个线程执行完成。
要将一个线程设置成守护线程,需要在启动线程之前执行如下语句:thread.daemon = True。同样,要检查线程的守护状态,也只需要检查这个值即可。一个新的线程会继承父线程的守护标记。整个 Python 程序将在所有非守护线程退出后才退出。
3.1、Thread 类
threading 模块的 Thread 类是主要的执行对象。
属性 | 说明 |
---|---|
Thread 对象数据属性 | |
name | 线程名 |
ident | 线程的标识符 |
daemon | 布尔标识,标识这个线程是否是守护线程 |
Thread 对象方法 | |
_init_(group=None,target(func)=None,name=None,args=(),kwargs={},verbose=None,daemon=None) | 实例化一个线程对象,需要有一个可调用的 target,以及其参数 args 或 kwargs。还可以传递 name 或 group 参数,不过后者还未实现。此外,verbose 标志也是可以接受的。而 daemon 的值也将会设定 thread.daemon 属性/标志 |
start() | 开始执行该进程 |
run() | 定义线程功能的方法(通常在子类中被应用开发者重写) |
join(timeout=None) | 直至启动的线程终止之前一直挂起;除非给出了 timeout,否则会一直阻塞 |
getName() | 返回线程名(更好的方法是设置(或获取)thread.name 属性,或者在实例化过程中传递该属性) |
setName() | 设定线程名(同上) |
isAlivel/is_alive() | 布尔标识,表示这个线程是否还存活 |
isDaemon() | 如果是守护线程,则返回 True;否则,返回 False(更好的方法是设置(或获取)thread.daemon 属性,或者在实例化过程中传递该属性) |
setDaemon(daemon) | 把线程的守护标识设定为布尔值 daemonic(必须在线程 start() 之前调用)(同上) |
使用 Thread 类,可以有很多种方法创建线程:
- 创建 Thread 的实例,传给它一个函数;
- 创建 Thread 的实际,传给它一个可调用的类实例;
- 派生 Thread 的子类,并创建子类的实例。
3.1.1、派生 Thread 的子类,并创建子类的实例
import threading
from time import sleep,ctime
loops = (4,2)
class MyThread(threading.Thread):
def __init__(self,func,args,name=''):
threading.Thread.__init__(self)
self.name = name
self.func = func
self.args = args
def run(self):
self.func(*self.args)
def loop(nloop,nsec):
print('start loop',nloop,'at:',ctime())
sleep(nsec)
print('loop',nloop,'done at:',ctime())
print('starting at:',ctime())
threads = []
nloops = range(len(loops))
for i in nloops:
t = MyThread(loop,(i,loops[i]),loop.__name__)
threads.append(t)
for i in nloops:
threads[i].start()
for i in nloops:
threads[i].join()
print('all DONE at:',ctime())
starting at: Thu Aug 20 16:09:44 2020
start loop 0 at: Thu Aug 20 16:09:44 2020
start loop