16.2. 线程-更高级别的线程接口

源代码: Lib/threading.py


该模块在较低级别的thread模块之上构造较高级别的线程接口。另请参见mutexQueue模块。

dummy_threading模块用于因缺少thread而无法使用threading的情况。

Note

从 Python 2.6 开始,此模块提供 PEP 8兼容的别名和属性,以替换受 Java 线程 API 启发的camelCase名称。此更新的 API 与multiprocessing模块的 API 兼容。但是,尚未设置弃用camelCase名称的时间表,Python 2.x 和 3.x 仍完全支持它们。

Note

从 Python 2.5 开始,如果错误调用,则多个 Thread 方法将引发RuntimeError而不是AssertionError

CPython 实现细节: 在 CPython 中,由于全局翻译锁,只有一个线程可以一次执行 Python 代码(即使某些面向性能的库可能克服了此限制)。如果希望您的应用程序更好地利用多核计算机的计算资源,建议您使用multiprocessing。但是,如果您要同时运行多个 I/O 绑定任务,则线程化仍然是合适的模型。

该模块定义以下Function和对象:

在 2.6 版中进行了更改:添加了active_count()拼写。

See Condition Objects.

在 2.6 版中进行了更改:添加了current_thread()拼写。

See Event Objects.

mydata = threading.local()
mydata.x = 1

对于单独的线程,实例的值将有所不同。

有关更多详细信息和大量示例,请参见_threading_local模块的文档字符串。

2.4 版的新Function。

See Lock Objects.

See RLock Objects.

See Semaphore Objects.

See Thread Objects.

See Timer Objects.

2.3 版的新Function。

2.3 版的新Function。

2.5 版的新Function。

对象的详细接口在下面记录。

该模块的设计大致基于 Java 的线程模型。但是,在 Java 使锁和条件变量成为每个对象的基本行为的地方,它们是 Python 中单独的对象。 Python 的Thread类支持 Java 的 Thread 类的行为的子集;当前,没有优先级,没有线程组,并且无法破坏,停止,挂起,恢复或break线程。 Java 的 Thread 类的静态方法在实现后会 Map 到模块级函数。

下述所有方法都是原子执行的。

16.2.1. 线程对象

此类表示在单独的控制线程中运行的活动。有两种指定活动的方法:pass将可调用对象传递给构造函数,或pass重写子类中的run()方法。子类中不应覆盖其他任何方法(构造函数除外)。换句话说,覆盖此类的init()run()方法。

创建线程对象后,必须pass调用线程的start()方法来启动其活动。这将在单独的控制线程中调用run()方法。

一旦线程的活动开始,该线程即被视为“活动”。当它的run()方法终止时,它会停止运行-无论是正常运行还是引发未处理的异常。 is_alive()方法测试线程是否处于活动状态。

其他线程可以调用线程的join()方法。这将阻塞调用线程,直到终止调用join()方法的线程为止。

线程具有名称。该名称可以传递给构造函数,并可以passname属性读取或更改。

线程可以标记为“守护程序线程”。该标志的重要性在于,仅保留守护程序线程时,整个 Python 程序都会退出。初始值是从创建线程继承的。可以passdaemon属性设置该标志。

Note

守护程序线程在关闭时突然停止。它们的资源(例如打开的文件,数据库事务等)可能无法正确释放。如果希望线程正常停止,请使其成为非守护线程,并使用合适的 signal 传递机制,例如Event

有一个“主线程”对象;这对应于 Python 程序中的初始控制线程。它不是守护程序线程。

有可能创建了“虚拟线程对象”。这些是与“外来线程”相对应的线程对象,“外来线程”是在线程模块外部启动的控制线程,例如直接从 C 代码开始的线程。虚拟线程对象的Function有限。它们始终被认为是活动的和守护程序,不能被join() ed。它们永远不会被删除,因为不可能检测到外来线程的终止。

如果子类覆盖了构造函数,则必须确保在对线程执行其他任何操作之前调用 Base Class 的构造函数(Thread.__init__())。

每个线程对象最多只能调用一次。它安排在单独的控制线程中调用对象的run()方法。

如果在同一线程对象上多次调用此方法,则将引发RuntimeError

您可以在子类中重写此方法。标准的run()方法调用传递给对象构造函数的可调用对象作为* target 参数(如果有),并分别从 args kwargs *参数中获取 Sequences 参数和关键字参数。

当存在* timeout *参数而不是None时,它应该是一个浮点数,以秒为单位(或其分数)指定操作的超时时间。由于join()始终返回None,因此您必须在join()之后调用isAlive()来确定是否发生超时–如果线程仍然存在,则join()调用超时。

当* timeout *参数不存在或None时,该操作将阻塞直到线程终止。

一个线程可以被多次join()编辑。

如果try加入当前线程,则join()引发RuntimeError,因为这将导致死锁。在启动线程之前join()一个线程也是错误的,try这样做会引发相同的异常。

2.6 版的新Function。

2.6 版的新Function。

此方法将在run()方法开始之前返回True,直到run()方法终止之后。模块函数enumerate()返回所有活动线程的列表。

在 2.6 版中进行了更改:添加了is_alive()拼写。

当没有活动的非守护线程时,整个 Python 程序将退出。

2.6 版的新Function。

16.2.2. 锁定物件

Primitives 锁是一种同步 Primitives,锁定后该 Primitives 不属于特定线程。在 Python 中,它是当前可用的最低级别的同步 Primitives,由thread扩展模块直接实现。

基本锁处于“已锁定”或“未锁定”两种状态之一。它是在解锁状态下创建的。它有两种基本方法,acquire()release()。当状态解锁时,acquire()将状态更改为锁定并立即返回。当状态锁定时,acquire()阻塞,直到在另一个线程中对release()的调用将其更改为未锁定,然后acquire()调用将其重置为锁定并返回。只能在锁定状态下调用release()方法。它将状态更改为解锁并立即返回。如果try释放解锁的锁,将引发ThreadError

acquire()中有多个线程被阻塞以 await 状态变为解锁时,当release()调用将状态重置为解锁时,只有一个线程 continue 前进;await 线程之一 continue 前进的过程尚未定义,并且可能因实现而异。

所有方法都是原子执行的。

在将* blocking *参数设置为True(默认值)的情况下调用时,阻塞直到锁被解锁,然后将其设置为 locked 并返回True

在将* blocking 参数设置为False的情况下调用时,请勿进行阻止。如果将 blocking *设置为True的呼叫阻塞,请立即返回False;否则,将锁设置为锁定并返回True

锁锁定后,将其重置为解锁状态,然后返回。如果其他任何线程被阻塞,await 锁解锁,则只允许其中一个 continue 进行。

在解锁的锁上调用时,将引发ThreadError

没有返回值。

16.2.3. RLock 对象

可重入锁是一个同步 Primitives,可以由同一线程多次获取。在内部,除了原始锁使用的锁定/解锁状态外,它还使用“拥有线程”和“递归级别”的概念。在锁定状态下,某些线程拥有该锁;在解锁状态下,没有线程拥有它。

要锁定该锁,线程调用其acquire()方法。一旦线程拥有锁,它将返回。要解锁该锁,线程将调用其release()方法。 acquire()/release()呼叫对可以嵌套;只有最后的release()(最外面的对中的release())会将锁重置为解锁状态,并允许在acquire()中阻塞的另一个线程 continue 进行。

在不带参数的情况下调用时:如果此线程已拥有锁,则将递归级别增加 1,然后立即返回。否则,如果另一个线程拥有该锁,则阻塞直到锁被解锁。一旦锁被解锁(不属于任何线程),然后获取所有权,将递归级别设置为 1,然后返回。如果阻塞了多个线程,直到锁被解锁,则一次只能获取一个线程的所有权。在这种情况下,没有返回值。

在将* blocking *参数设置为 true 的情况下调用时,请执行与不带参数的调用相同的操作,然后返回 true。

在将* blocking *参数设置为 false 的情况下调用时,请勿阻塞。如果没有参数的调用将阻塞,则立即返回 false;否则,返回 false。否则,执行与不带参数调用时相同的操作,并返回 true。

仅在调用线程拥有锁时才调用此方法。如果在解锁时调用此方法,则会引发RuntimeError

没有返回值。

16.2.4. 条件对象

条件变量总是与某种锁定相关联。可以传入,也可以默认创建一个。 (当多个条件变量必须共享相同的锁时,传递一个 Importing 很有用.)

条件变量具有acquire()release()方法,它们调用关联锁的相应方法。它还具有wait()方法以及notify()notifyAll()方法。仅当调用线程获得了锁时才必须调用这三个对象,否则引发RuntimeError

wait()方法释放该锁,然后阻塞,直到被另一个线程中的相同条件变量的notify()notifyAll()调用唤醒为止。唤醒后,它将重新获取锁并返回。也可以指定超时。

notify()方法唤醒一个 await 条件变量的线程(如果有)在 await。 notifyAll()方法唤醒所有 await 条件变量的线程。

注意:notify()notifyAll()方法不会释放该锁;这意味着唤醒的一个或多个线程不会立即从其wait()调用中返回,而是仅在调用notify()notifyAll()的线程finally放弃该锁的所有权时才返回。

提示:使用条件变量的典型编程样式使用锁来同步对某些共享状态的访问;对状态的特定更改感兴趣的线程会反复调用wait(),直到看到所需的状态为止;而对状态进行修改的线程会在它们以某种可能成为所需状态的方式更改状态时调用notify()notifyAll()。那些服务员。例如,以下代码是具有无限缓冲区容量的一般生产者-Consumer 情况:

# 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()之间进行选择,请考虑只对一个或几个 await 线程进行一次状态更改是否有意义。例如。在典型的生产者-Consumer 情况下,向缓冲区添加一项仅需要唤醒一个 Consumer 线程。

此方法释放基础锁,然后进行阻塞,直到被另一个线程中的相同条件变量的notify()notifyAll()调用唤醒为止,或者直到发生可选的超时为止。一旦唤醒或超时,它将重新获取锁并返回。

当存在* timeout *参数而不是None时,它应该是一个浮点数,用于指定操作的超时时间(以秒为单位)。

当基础锁是RLock时,不会使用其release()方法释放它,因为当递归多次获取锁时,它实际上可能无法解锁。而是使用RLock类的内部接口,即使递归获取了几次,它也可以 true 将其解锁。重新获得锁定后,另一个内部接口将用于恢复递归级别。

该方法最多唤醒* n *个 await 条件变量的线程;如果没有线程正在 await,则为空操作。

如果至少有* n 个线程正在 await,则当前实现会精确地唤醒 n 个线程。但是,依靠这种行为并不安全。Future 的优化实现有时可能会唤醒 n *个线程。

注意:唤醒的线程直到可以重新获取锁定后才 true 从其wait()调用返回。由于notify()不会释放锁,因此它的调用方应该。

在 2.6 版中进行了更改:添加了notify_all()拼写。

16.2.5. signal 量对象

这是计算机科学史上最古老的同步 Primitives 之一,由荷兰早期计算机科学家 Edsger W. Dijkstra 发明(他使用P()V()而不是acquire()release())。

signal 量 Management 一个内部计数器,该内部计数器由每个acquire()调用递减,并由每个release()调用递增。计数器永远不能低于零。当acquire()发现它为零时,它将阻塞,直到其他线程调用release()为止。

当不带参数调用时:如果内部计数器在 Importing 时大于零,则将其递减 1 并立即返回。如果在进入时为零,则阻塞,await 其他线程调用release()使其大于零。这是pass适当的互锁完成的,因此,如果多个acquire()呼叫被阻止,则release()会恰好唤醒其中一个。该实现可能会随机选择一个,因此不应依赖唤醒被阻塞线程的 Sequences。在这种情况下,没有返回值。

在将* blocking *设置为 true 的情况下调用时,请执行与不带参数的调用相同的操作,然后返回 true。

在将* blocking *设置为 false 的情况下调用时,请勿进行阻止。如果没有参数的调用将阻塞,则立即返回 false;否则,返回 false。否则,执行与不带参数调用时相同的操作,并返回 true。

16.2.5.1. signal 量示例

signal 量通常用于保护容量有限的资源,例如数据库服务器。在资源大小固定的任何情况下,都应使用有界 signal 量。在产生任何工作线程之前,您的主线程将初始化 signal 量:

maxconnections = 5
...
pool_sema = BoundedSemaphore(value=maxconnections)

产生后,辅助线程在需要连接到服务器时会调用 signal 量的获取和释放方法:

pool_sema.acquire()
conn = connectdb()
... use connection ...
conn.close()
pool_sema.release()

有界 signal 量的使用减少了导致 signal 量被释放超过获取量的编程错误不会被检测到的机会。

16.2.6. 事件对象

这是线程之间通信的最简单机制之一:一个线程发出事件 signal,其他线程 await 事件。

事件对象 Management 一个内部标志,该标志可以passset()方法设置为 true,而可以passclear()方法重置为 false。 wait()方法将阻塞,直到该标志为 true。

在 2.6 版中进行了更改:添加了is_set()拼写。

当存在 timeout 参数而不是None时,它应该是一个浮点数,以秒(或其分数)指定操作的超时时间。

此方法在退出时返回内部标志,因此,除非给出超时且操作超时,否则它将始终返回True

在 2.7 版中进行了更改:以前,该方法始终返回None

16.2.7. 计时器对象

此类表示仅在经过一定时间后才应执行的操作-计时器。 TimerThread的子类,因此也可作为创建自定义线程的示例。

与线程一样,计时器pass调用其start()方法来启动。可以pass调用cancel()方法来停止计时器(在其动作开始之前)。计时器在执行其操作之前将 await 的时间间隔可能与用户指定的时间间隔不完全相同。

For example:

def hello():
    print "hello, world"

t = Timer(30.0, hello)
t.start()  # after 30 seconds, "hello, world" will be printed

16.2.8. 在 with 语句中使用锁,条件和 signal 量

此模块提供的具有acquire()release()方法的所有对象都可用作with语句的上下文 Management 器。进入该块时将调用acquire()方法,而退出该块时将调用release()

当前,LockRLockConditionSemaphoreBoundedSemaphore对象可用作with语句上下文 Management 器。例如:

import threading

some_rlock = threading.RLock()

with some_rlock:
    print "some_rlock is locked while this executes"

16.2.9. 导入线程代码

虽然导入机制是线程安全的,但由于提供线程安全的方式存在固有的限制,因此对线程导入有两个关键限制:

首页