threading
线程 - 更高级的线程接口
源代码:
Lib / threading.py
该模块在较低级别的thread
模块上构建较高级别的线程接口。另请参阅mutex
和Queue
模块。
dummy_threading
模块适用于threading
因thread
缺失而无法使用的情况。
注意
从Python 2.6开始,该模块提供了符合PEP 8的
别名和属性,以取代camelCase
受Java的线程API启发的名称。这个更新的API与multiprocessing
模块的兼容。但是,没有为camelCase
名称的弃用设置任何计划,并且它们在Python 2.x和3.x中都得到完全支持。
注意
从Python 2.5开始,几个Thread方法会引发,RuntimeError
而不是AssertionError
被错误地调用。
CPython实现细节:
在CPython中,由于全局解释器锁定,只有一个线程可以一次执行Python代码(即使某些面向性能的库可能会克服此限制)。如果您希望您的应用程序更好地利用多核机器的计算资源,建议您使用multiprocessing
。但是,如果要同时运行多个I / O限制任务,线程仍然是一个合适的模型。
该模块定义了以下功能和对象:
threading.active_count()threading.activeCount()
返回Thread
当前活着的对象数量。返回的计数等于返回的列表的长度enumerate()
。
在版本2.6中更改:添加了active_count()
拼写。
threading.Condition()
一个返回一个新的条件变量对象的工厂函数。一个条件变量允许一个或多个线程等待,直到它们被另一个线程通知。
请参阅条件对象。
threading.current_thread()threading.currentThread()
返回当前Thread
对象,对应于调用者的控制线程。如果调用者的控制线程不是通过threading
模块创建的,则返回具有有限功能的虚拟线程对象。
在版本2.6中更改:添加了current_thread()
拼写。
threading.enumerate()
返回Thread
当前活着的所有对象的列表。该列表包括守护进程线程,由其创建的虚拟线程对象current_thread()
以及主线程。它排除了尚未启动的已终止的线程和线程。
threading.Event()
一个返回新事件对象的工厂函数。一个事件管理一个标志,该标志可以通过该set()
方法设置为true,并通过该方法重置为false clear()
。该wait()
方法阻塞直到标志为真。
请参阅事件对象。
class threading.local
表示线程本地数据的类。线程本地数据是其值为线程特定的数据。要管理线程本地数据,只需创建一个local
(或一个子类)的实例并在其上存储属性:
mydata = threading.local()
mydata.x = 1
实例的值对于单独的线程将会有所不同。
有关更多详细信息和广泛的示例,请参阅_threading_local
模块的文档字符串。
2.4版本中的新功能。
threading.Lock()
工厂函数,返回一个新的原始锁定对象。一旦一个线程获得它,后续的获取它的尝试就会被阻止,直到它被释放; 任何线程都可能释放它。
请参见锁定对象。
threading.RLock()
一个返回新的可重入锁定对象的工厂函数。重入锁必须由获取它的线程释放。一旦一个线程获得了一个可重入的锁,同一个线程可以再次获得它而不会阻塞; 线程每次获取它时都必须释放一次。
请参阅RLock对象。
threading.Semaphore([value])
一个返回一个新的信号量对象的工厂函数。信号量管理计数器,表示release()
呼叫数量减去acquire()
呼叫数量加上初始值
。该acquire()
方法根据需要进行阻止,直到它可以返回而不使计数器为负。如果没有给出,值
默认为1。
请参阅信号量对象。
threading.BoundedSemaphore([value])
一个返回一个新的有界信号量对象的工厂函数。有界信号量会检查以确保其当前值
不超过其初始值
。如果有,ValueError
就提出来。在大多数情况下,信号量用于保护容量有限的资源。如果信号量被释放太多次,这是一个错误的迹象。如果没有给出,值
默认为1。
class threading.Thread
表示控制线程的类。这门课可以安全地以有限的方式进行分类。
请参阅线程对象。
class threading.Timer
经过指定时间间隔后执行函数的线程。
见计时器对象。
threading.settrace(func)
为从threading
模块启动的所有线程设置跟踪功能。该FUNC
将被传递给sys.settrace()
每个线程,它之前run()
被调用的方法。
2.3版本的新功能。
threading.setprofile(func)
为从threading
模块启动的所有线程设置配置文件功能。该FUNC
将被传递给sys.setprofile()
每个线程,它之前run()
被调用的方法。
2.3版本的新功能。
threading.stack_size([size])
返回创建新线程时使用的线程堆栈大小。可选的size参数指定要用于随后创建的线程的堆栈大小,并且必须为0(使用平台或配置的默认值)或至少32,768(32 KiB)的正整数值。如果未指定大小,则使用0。如果不支持更改线程堆栈大小,ThreadError则会引发a。如果指定的堆栈大小无效,aValueError并且堆栈大小未修改。32kB是目前支持的最小堆栈大小值,以保证解释器本身具有足够的堆栈空间。请注意,某些平台可能对堆栈大小的值有特殊限制,例如要求最小堆栈大小> 32kB或需要以系统内存页大小的倍数进行分配 - 有关更多信息,应参考平台文档(4kB页是常见的;对于堆栈大小,使用4096的倍数是没有更具体信息的建议方法)。可用性:Windows,带有POSIX线程的系统。
2.5版本中的新功能。
exception threading.ThreadError
如下所述引发了各种线程相关的错误。请注意,许多接口使用,RuntimeError
而不是ThreadError
。
下面介绍了这些对象的详细接口。
这个模块的设计松散地基于Java的线程模型。但是,在Java为锁和条件变量生成每个对象的基本行为的地方,它们是Python中的单独对象。Python的Thread
类支持Java的Thread
类的一部分行为; 目前没有优先级,没有线程组,线程不能被销毁,停止,暂停,恢复或中断。Java的Thread
类的静态方法在实现时映射到模块级函数。
下面描述的所有方法都是以原子方式执行的。
1.线程对象
这个类表示一个在单独的控制线程中运行的活动。有两种方法可以指定活动:将可调用对象传递给构造函数,或者通过重写run()
子类中的方法。在子类中不应该重写其他方法(构造函数除外)。换句话说,仅
覆盖__init__()
和run()
这个类的方法。
一旦线程对象被创建,它的活动必须通过调用线程的start()
方法来启动。这将run()
在一个单独的控制线程中调用该方法。
一旦线程的活动开始,线程被认为是“活着的”。当它的run()
方法终止时,它会停止活动- 通常或者通过引发未处理的异常。is_alive()
方法测试线程是否存在。
其他线程可以调用线程的join()
方法。这会阻塞调用线程,直到其join()
方法被调用的线程终止。
一个线程有一个名字。该名称可以传递给构造函数,并通过name
属性读取或更改。
线程可以被标记为“守护线程”。这个标志的意义在于,只有守护进程线程退出时,整个Python程序才会退出。初始值是从创建线程继承的。该标志可以通过daemon
属性设置。
注意
守护程序线程在关机时突然停止。他们的资源(如打开文件,数据库事务等)可能无法正确释放。如果你想让你的线程正常停止,使它们不是守护进程,并使用合适的信号机制,如Event
。
有一个“主线程”对象; 这对应于Python程序中的初始控制线程。它不是一个守护线程。
有可能会创建“虚拟线程对象”。这些是与“外来线程”对应的线程对象,它们是在线程模块外部启动的控制线程,例如直接来自C代码。虚拟线程对象的功能有限; 他们总是被认为是活着和神圣的,而且不能被join()
编辑。它们从不被删除,因为不可能检测到外来线程的终止。
class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})
应始终使用关键字参数调用此构造函数。参数是:
组
应该是None
;在ThreadGroup
实施课程时预留给将来的延期。
target
是要由run()
方法调用的可调用对象。默认为None
,意味着什么都不叫。
名称
是线程名称
。默认情况下,一个唯一的名称
由“Thread- N
” 形式构成,其中N
是小数字。
args
是目标调用的参数元组。默认为()
。
kwargs
是目标调用的关键字参数字典。默认为{}
。
如果子类重写构造函数,则必须确保Thread.__init__()
在对线程执行任何其他操作之前调用基类构造函数()。
start()
开始线程的活动。
每个线程对象最多只能调用一次。它安排run()
在独立的控制线程中调用对象的方法。
这个方法会RuntimeError
在同一个线程对象上多次调用一次。
run()
表示线程活动的方法。
您可以在子类中重写此方法。标准run()
方法调用传递给对象构造函数的可调用对象作为目标
参数,如果有的话,分别从args
和kwargs
参数中获取顺序和关键字参数。
join([timeout])
等到线程终止。这会阻塞调用线程,直到调用其join()
方法的线程终止 - 通常或通过未处理的异常 - 或直到发生可选的超时。
如果超时
参数不存在None
,它应该是一个浮点数,以秒为单位指定该操作的超时
值(或其分数)。一如join()
往常返回None
,您必须调用isAlive()
after join()
以决定是否发生超时
- 如果线程仍处于活动状态,则join()
呼叫超时
。
当timeout
参数不存在时None
,该操作将阻塞,直到线程终止。
一个线程可以被join()
多次编辑。
join()
提出了RuntimeError
如果试图加入当前线程因为这将导致死锁。join()
在线程启动之前它也是一个错误,并且尝试这样做会引发相同的异常。
name
一个字符串,仅用于识别目的。它没有语义。多个线程可以被赋予相同的名称。初始名称由构造函数设置。
2.6版本中的新功能。
getName()setName()
2.6之前的API name
。
ident
此线程的'线程标识符'或者None
线程尚未启动。这是一个非零整数。查看thread.get_ident()
功能。当线程退出并创建另一个线程时,线程标识符可能会被回收。该标识符即使在线程退出后也可用。
2.6版本中的新功能。
is_alive()isAlive()
返回线程是否存在。
该方法在run()
方法开始之前返回True
,直到run()
方法终止。模块函数enumerate()
返回所有活动线程的列表。
在版本2.6中更改:添加了is_alive()
拼写。
daemon
指示此线程是否为守护程序线程(True)或不(False
)的布尔值。这必须在start()
调用之前设置,否则RuntimeError
会引发。它的初始值是从创建线程继承的; 主线程不是守护进程线程,因此在主线程中创建的所有线程均默认为daemon
= False
。
当没有活动的非守护进程线程时,整个Python程序将退出。
2.6版本中的新功能。
isDaemon()setDaemon()
2.6之前的API daemon
。
2.锁定对象
原始锁定是一种同步原语,在锁定时不属于特定线程。在Python中,它是当前可用的最低级同步原语,由thread
扩展模块直接实现。
原始锁处于“锁定”或“解锁”两种状态之一。它在解锁状态下创建。它有两个基本的方法,acquire()
和release()
。状态解锁时,acquire()
将状态更改为锁定并立即返回。当状态被锁定时,acquire()
阻塞直到release()
在另一个线程中的呼叫将其更改为解锁,然后acquire()
呼叫将其重置为锁定并返回。该release()
方法只能在锁定状态下调用; 它将状态更改为解锁并立即返回。如果试图释放未锁定的锁,ThreadError
则会提出。
当多个线程在acquire()
等待状态转为解锁状态时被阻塞时,当一个release()
呼叫将状态重置为解锁状态时,只有一个线程继续进行; 哪一个等待的线程没有被定义,并且可能会因实现而有所不同。
所有的方法都是自动执行的。
Lock.acquire([blocking])
获取锁定,阻止或不阻止。
当调用阻塞
参数设置为True
(默认)时,阻塞
直到锁定解锁,然后将其设置为锁定并返回True
。
当调用阻塞
参数设置为False
,不要阻塞
。如果阻塞
设置的呼叫True
会阻塞
,False
立即返回; 否则,将锁设置为锁定并返回True
。
Lock.release()
释放一个锁。
锁定被锁定时,将其重置为解锁状态,然后返回。如果有其他线程被阻塞,等待锁被解锁,请准确地让其中的一个继续。
在解锁的锁上调用时,ThreadError
会引发a。
没有返回值。
3. RLock对象
可重入锁定是同一个原语,它可能被同一个线程多次获取。在内部,除了原始锁使用的锁定/解锁状态外,它还使用“拥有线程”和“递归级别”的概念。在锁定状态下,某个线程拥有该锁; 在解锁状态下,没有线程拥有它。
为了锁定锁定,一个线程调用它的acquire()
方法; 这将在线程拥有锁定时返回。为了解锁,一个线程调用它的release()
方法。acquire()
/ release()
调用对可以嵌套; 只有最后一对release()
(release()
最外端的一对)重置锁以解锁,并允许另一个线程被阻止acquire()
进行。
RLock.acquire([blocking=1])
获取锁定,阻止或不阻止。
在没有参数的情况下调用时:如果此线程已拥有该锁,则递归级别递增1,并立即返回。否则,如果另一个线程拥有该锁,则阻塞直到该锁被解锁。一旦锁定被解锁(不属于任何线程),然后获取所有权,将递归级别设置为1,然后返回。如果有多个线程被阻塞,等待锁定解锁,则一次只能有一个线程能够获取该锁的所有权。在这种情况下没有返回值。
当使用设置为true 的阻塞
参数进行调用时,请执行与不使用参数时调用相同的操作,并返回true。
当将blocking
参数设置为false 时调用时,请勿阻止。如果没有参数的调用会被阻塞,立即返回false;否则,与没有参数调用时一样,返回true。
RLock.release()
释放一个锁,递减递归级别。如果在递减之后它为零,则将锁重置为解锁(不属于任何线程),并且如果有其他线程被阻塞,等待锁解锁,请准确地让其中一个继续。如果在递减之后递归级别仍然不为零,则锁保持锁定并由调用线程拥有。
只有在调用线程拥有锁定时才调用此方法。如果在解锁锁定时调用此方法,则会引发RuntimeError
。
没有返回值。
4.条件对象
一个条件变量总是与某种锁相关联; 这可以通过或默认创建一个。(当多个条件变量必须共享同一个锁时,传入一个是很有用的。)
条件变量具有acquire()
与release()
该调用相关联的锁的相应方法的方法。它也有一个wait()
方法,notify()
和notifyAll()
方法。这三个只能在调用线程获得锁的时候调用,否则会引发RuntimeError
。
wait()
方法释放锁,然后阻塞,直到它被另一个线程中的相同条件变量唤醒notify()
或notifyAll()
调用。一旦醒来,它重新获得锁定并返回。也可以指定超时。
notify()
方法唤醒等待条件变量的线程之一,如果有任何正在等待的话。该notifyAll()
方法唤醒等待条件变量的所有线程。
注意:notify()
和notifyAll()
方法不释放锁; 这意味着被唤醒的一个或多个线程不会wait()
立即从它们的调用中返回,而只是在调用notify()
或notifyAll()
最终放弃锁的所有权的线程时。
提示:使用条件变量的典型编程风格使用锁来同步对某些共享状态的访问; 那些对特定的状态改变感兴趣的线程,wait()
直到他们看到所需的状态,而修改状态调用的线程notify()
或者notifyAll()
当他们改变状态时,这些线程可能成为其中一个服务员的期望状态。例如,以下代码是具有无限缓冲区容量的通用生产者 - 消费者情况:
# Consume one item
cv.acquire()
while not an_item_is_available():
cv.wait()
get_an_available_item()
cv.release()
# Produce one item
cv.acquire()
make_an_item_available()
cv.notify()
cv.release()
要选择notify()
和notifyAll()
,考虑一个状态变化是否可以只为一个或几个等待的线程有趣。例如,在典型的生产者 - 消费者情况下,向缓冲区添加一个项目只需要唤醒一个消费者线程。
class threading.Condition([lock])
如果给出了lock
参数而不是None
,它必须是一个Lock
或者一个RLock
对象,并且它被用作底层锁。否则,将RLock
创建一个新对象并将其用作基础锁。
acquire(*args)
获取底层锁。该方法调用底层锁的相应方法; 返回值是该方法返回的值。
release()
释放底层的锁。该方法调用底层锁的相应方法; 没有返回值。
wait([timeout])
等到通知或直到发生超时。如果在调用此方法时调用线程未获取锁,则会引发RuntimeError
。
此方法释放底层锁,然后阻塞,直到它被另一个线程中的相同条件变量唤醒notify()
或notifyAll()
调用,直到发生可选超时。一旦被唤醒或超时,它将重新获得锁定并返回。
如果超时
参数不存在None
,它应该是一个浮点数,以秒为单位指定该操作的超时
值(或其分数)。
当底层锁是an时RLock
,它不会使用它的release()
方法释放,因为当递归获取多次时,这实际上可能不会解锁该锁。相反,使用RLock
该类的内部接口,即使已递归获取多次,也可以真正解锁该接口。当锁重新获得时,另一个内部接口用于恢复递归级别。
notify(n=1)
默认情况下,唤醒等待此条件的一个线程(如果有)。如果在调用此方法时调用线程未获取锁,RuntimeError
则会引发a。
此方法至多唤醒n个
等待条件变量的线程; 如果没有线程正在等待,则它是无操作的。
如果至少有n个
线程正在等待,那么当前实现将醒来n 个
线程。但是,依靠这种行为是不安全的。未来,优化的实现可能偶尔唤醒超过n个
线程。
注意:一个唤醒的线程实际上并没有从它的wait()
调用返回,直到它可以重新获得锁。由于notify()
不释放锁,因此其调用者应该。
notify_all()notifyAll()
唤醒等待这种情况的所有线程。这个方法的作用就像notify()
,但唤醒所有等待的线程而不是一个。如果在调用此方法时调用线程未获取锁,则会引发RuntimeError
。
在版本2.6中更改:添加了notify_all()
拼写。
5.信号量对象
这是计算机科学史上最古老的同步原语,由早期的荷兰计算机科学家艾兹赫尔·戴克斯特拉发明之一(他用P()
和V()
,而不是acquire()
和release()
)。
信号量管理一个内部计数器,每次acquire()
调用都会减少内部计数器,并随每次调用而递增release()
。柜台不能低于零; 当acquire()
发现它为零时,它会阻塞,等待其他线程调用release()
。
class threading.Semaphore([value])
可选参数给出了内部计数器的初始值
;它默认为1
。如果给定的值
小于0,ValueError
则升高。
acquire([blocking])
获取信号量。
当不带参数调用时:如果内部计数器在输入时大于零,则将其减1并立即返回。如果入口处为零,则阻塞,等待其他线程调用release()
使其大于零。这是通过适当的互锁来完成的,这样如果多个acquire()
呼叫被阻止,release()
它们中的一个就会被唤醒。实现可以随机选择一个,所以不应该依赖被阻塞的线程被唤醒的顺序。在这种情况下没有返回值。
当调用block
设置为true时,执行与没有参数调用时相同的操作,并返回true。
当调用阻塞
设置为false时,不要阻塞
。如果没有参数的调用会被阻塞
,立即返回false; 否则,与没有参数调用时一样,返回true。
release()
释放一个信号量,将内部计数器递增1。当它在入口处为零并且另一个线程正在等待它再次变得大于零时,唤醒该线程。
5.1. Semaphore例
信号量通常用于防范容量有限的资源,例如数据库服务器。在任何情况下,资源的大小是固定的,你应该使用有界的信号量。在产生任何工作线程之前,主线程会初始化信号量:
maxconnections = 5
...
pool_sema = BoundedSemaphore(value=maxconnections)
一旦产生,工作线程在需要连接到服务器时调用信号量的获取和释放方法:
pool_sema.acquire()
conn = connectdb()
... use connection ...
conn.close()
pool_sema.release()
有界信号量的使用降低了导致信号量被释放的程序错误超过它所获得的错误的可能性。
6.事件对象
这是线程间最简单的通信机制之一:一个线程表示一个事件,其他线程等待它。
事件对象管理一个内部标志,该标志可以通过该set()
方法设置为true,并通过该方法重置为false clear()
。该wait()
方法阻塞直到标志为真。
class threading.Event
内部标志最初是错误的。
is_set()isSet()
当且仅当内部标志为真时返回true。
在版本2.6中更改:添加了is_set()
拼写。
set()
将内部标志设置为true。所有等待它成为真的线程都被唤醒。wait()
一旦标志为真,调用的线程根本不会阻塞。
clear()
将内部标志重置为false。随后,线程调用wait()
将被阻塞,直到set()
被调用以将内部标志再次设置为真。
wait([timeout])
阻塞直到内部标志为真。如果内部标志在输入时为真,则立即返回。否则,阻塞直到另一个线程调用set()
将该标志设置为true,或者直到发生可选的超时。
如果超时参数不存在None
,它应该是一个浮点数,以秒为单位指定该操作的超时值(或其分数)。
此方法在退出时返回内部标志,因此除非发生超时并且操作超时,否则它将始终返回True
。
在版本2.7中更改:以前,该方法始终返回None
。
7.计时器对象
这个类表示一个只有经过一段时间才能运行的动作 - 一个计时器。Timer
是其子类,Thread
因此也可以作为创建自定义线程的示例。
定时器和线程一样,通过调用它们的start()
方法来启动。通过调用该cancel()
方法可以停止定时器(在动作开始之前)。计时器在执行其动作之前等待的时间间隔可能与用户指定的时间间隔不完全相同。
例如:
def hello():
print "hello, world"
t = Timer(30.0, hello)
t.start() # after 30 seconds, "hello, world" will be printed
class threading.Timer(interval, function, args=[], kwargs={})
创建一个计时器,它会在时间间隔
秒后通过参数args
和关键字参数kwargs
运行函数
。
cancel()
停止定时器,并取消定时器动作的执行。这只有在计时器仍处于等待阶段时才有效。
8.在with语句中使用锁,条件和信号量
该模块提供的所有对象acquire()
和release()
方法都可以用作with
语句的上下文管理器。该acquire()
方法将在块被输入release()
时调用,并在块退出时调用。
目前Lock
,RLock
,Condition
,Semaphore
,和BoundedSemaphore
对象可以用作with
声明上下文管理。例如:
import threading
some_rlock = threading.RLock()
with some_rlock:
print "some_rlock is locked while this executes"
9.导入线程代码
尽管导入机器是线程安全的,但由于线程安全性的固有限制,导入导入有两个关键限制:
- 首先,除了在主模块中之外,导入不应具有产生新线程然后以任何方式等待该线程的副作用。如果产生的线程直接或间接尝试导入模块,不遵守此限制会导致死锁。
- 其次,在解释者开始关闭之前,所有的输入尝试都必须完成。只需执行通过线程模块创建的非守护线程的导入就可以轻松实现此目的。使用线程模块直接创建的守护程序线程和线程将需要一些其他形式的同步,以确保在系统关闭开始后它们不会尝试导入。不遵守这一限制会导致解释器关闭期间的间歇性异常和崩溃(因为延迟进口试图访问不再处于有效状态的机器)。