16.2. threading — 高级线程接口 — Python 文档

来自菜鸟教程
Python/docs/2.7/library/threading
跳转至:导航、​搜索

16.2. 穿线 — 高级线程接口

源代码: :source:`Lib/threading.py`



该模块在较低级别的 thread 模块之上构建了更高级别的线程接口。 另请参阅 mutexQueue 模块。

dummy_threading 模块提供用于 threading 无法使用的情况,因为缺少 thread

笔记

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


笔记

从 Python 2.5 开始,如果错误调用,一些 Thread 方法会引发 RuntimeError 而不是 AssertionError


该模块定义了以下函数和对象:

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,并使用 clear() 方法重置为 false。 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() 方法在必要时阻塞,直到它可以返回而不使计数器为负。 如果没有给出,value 默认为 1。

请参阅 信号量对象

threading.BoundedSemaphore([value])
返回新的有界信号量对象的工厂函数。 有界信号量检查以确保其当前值不超过其初始值。 如果是,则 ValueError 升高。 在大多数情况下,信号量用于保护容量有限的资源。 如果信号量被释放太多次,则表明存在错误。 如果没有给出,value 默认为 1。
class threading.Thread

表示控制线程的类。 这个类可以以有限的方式安全地进行子类化。

请参阅 线程对象

class threading.Timer

在指定的时间间隔过后执行函数的线程。

请参阅 定时器对象

threading.settrace(func)

为从 threading 模块启动的所有线程设置跟踪函数。 func 将在每个线程的 run() 方法被调用之前传递给 sys.settrace()

2.3 版中的新功能。

threading.setprofile(func)

为从 threading 模块启动的所有线程设置配置文件函数。 func 将在每个线程的 run() 方法被调用之前传递给 sys.setprofile()

2.3 版中的新功能。

threading.stack_size([size])

返回创建新线程时使用的线程堆栈大小。 可选的 size 参数指定用于后续创建的线程的堆栈大小,并且必须为 0(使用平台或配置的默认值)或至少为 32,768 (32 KiB) 的正整数值。 如果未指定 size,则使用 0。 如果不支持更改线程堆栈大小,则会引发 ThreadError。 如果指定的堆栈大小无效,则会引发 ValueError 并且堆栈大小未修改。 32kB 是当前支持的最小堆栈大小值,以保证解释器本身有足够的堆栈空间。 请注意,某些平台可能对堆栈大小的值有特定限制,例如要求最小堆栈大小 > 32kB 或要求以系统内存页面大小的倍数进行分配 - 应参考平台文档以获取更多信息(4kB 页面很常见) ;如果没有更具体的信息,建议使用 4096 的倍数作为堆栈大小)。 可用性:Windows,具有 POSIX 线程的系统。

2.5 版中的新功能。

exception threading.ThreadError
引发各种线程相关的错误,如下所述。 请注意,许多接口使用 RuntimeError 而不是 ThreadError

对象的详细接口记录如下。

该模块的设计松散地基于 Java 的线程模型。 然而,Java 使锁和条件变量成为每个对象的基本行为,它们在 Python 中是独立的对象。 Python 的 Thread 类支持 Java 的 Thread 类的行为子集; 目前,没有优先级,没有线程组,线程不能被销毁、停止、暂停、恢复或中断。 Java Thread 类的静态方法在实现时被映射到模块级函数。

下面描述的所有方法都是以原子方式执行的。

16.2.1. 线程对象

此类表示在单独的控制线程中运行的活动。 有两种方法可以指定活动:通过将可调用对象传递给构造函数,或者通过覆盖子类中的 run() 方法。 不应在子类中覆盖其他方法(构造函数除外)。 换句话说,only 覆盖了这个类的 __init__()run() 方法。

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

一旦线程的活动开始,线程就被认为是“活动的”。 当它的 run() 方法终止时,它停止活动——正常情况下,或者通过引发未处理的异常。 is_alive() 方法测试线程是否处于活动状态。

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

一个线程有一个名字。 该名称可以传递给构造函数,并通过 name 属性读取或更改。

一个线程可以被标记为“守护线程”。 这个标志的意义在于,当只剩下守护线程时,整个 Python 程序就会退出。 初始值是从创建线程继承的。 该标志可以通过 daemon 属性设置。

笔记

守护线程在关机时突然停止。 他们的资源(如打开的文件、数据库事务等)可能无法正常释放。 如果您希望您的线程正常停止,请将它们设为非守护进程并使用合适的信号机制,例如 Event


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

有可能创建“虚拟线程对象”。 这些是与“外来线程”相对应的线程对象,它们是在线程模块之外启动的控制线程,例如直接从 C 代码启动。 虚拟线程对象的功能有限; 它们总是被认为是活着的和恶魔般的,并且不能被 join() ed。 它们永远不会被删除,因为不可能检测到外来线程的终止。

class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})

应始终使用关键字参数调用此构造函数。 参数是:

应为None; 为将来实现 ThreadGroup 类时的扩展保留。

target 是由 run() 方法调用的可调用对象。 默认为 None,意味着什么都不调用。

name 是线程名称。 默认情况下,唯一名称的构造形式为“Thread-N”,其中 N 是一个小十进制数。

args 是目标调用的参数元组。 默认为 ()

kwargs 是目标调用的关键字参数字典。 默认为 {}

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

start()

启动线程的活动。

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

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

run()

表示线程活动的方法。

您可以在子类中覆盖此方法。 标准的 run() 方法调用传递给对象构造函数的可调用对象作为 target 参数,如果有的话,顺序和关键字参数取自 argskwargs 参数,分别。

join([timeout])

等待线程终止。 这会阻塞调用线程,直到调用其 join() 方法的线程终止 - 通常或通过未处理的异常 - 或直到发生可选超时。

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

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

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

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

name

仅用于识别目的的字符串。 它没有语义。 多个线程可能会被赋予相同的名称。 初始名称由构造函数设置。

2.6 版中的新功能。

getName()
setName()

name 的 2.6 之前的 API。

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()

daemon 的 2.6 之前的 API。


16.2.2. 锁定对象

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

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

acquire()中多个线程被阻塞等待状态转为unlocked时,当release()调用将状态重置为unlocked时,只有一个线程继续执行; 未定义等待线程中的哪一个进行,并且可能因实现而异。

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

Lock.acquire([blocking])

获取锁,阻塞或非阻塞。

当调用 blocking 参数设置为 True(默认值)时,阻塞直到锁被解锁,然后将其设置为锁定并返回 True

当调用 blocking 参数设置为 False 时,不要阻塞。 如果 blocking 设置为 True 的调用会阻塞,则立即返回 False; 否则,设置锁为locked并返回True

Lock.release()

释放锁。

当锁被锁定时,将其重置为解锁,然后返回。 如果任何其他线程在等待锁定解锁时被阻塞,则只允许其中一个线程继续执行。

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

没有返回值。

locked()
Return true if the lock is acquired.


16.2.3. RLock 对象

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

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

RLock.acquire([blocking=1])

获取锁,阻塞或非阻塞。

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

blocking 参数设置为 true 的情况下调用时,执行与不带参数调用时相同的操作,并返回 true。

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

RLock.release()

释放锁,递减递归级别。 如果递减后它为零,则将锁重置为解锁(不属于任何线程),并且如果任何其他线程被阻塞等待锁解锁,则只允许其中一个线程继续进行。 如果递减后递归级别仍然不为零,则锁保持锁定并由调用线程拥有。

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

没有返回值。


16.2.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,则它必须是 LockRLock 对象,并用作底层锁. 否则,将创建一个新的 RLock 对象并将其用作底层锁。

acquire(*args)

获取底层锁。 该方法调用底层锁上的相应方法; 返回值是该方法返回的任何值。

release()

释放底层锁。 该方法调用底层锁上的相应方法; 没有返回值。

wait([timeout])

等到通知或直到发生超时。 如果调用此方法时调用线程尚未获取锁,则会引发 RuntimeError

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

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

当底层锁是 RLock 时,它不会使用其 release() 方法释放,因为当它被多次递归获取时,这可能实际上不会解锁锁。 取而代之的是,使用了 RLock 类的内部接口,即使在多次递归获取时,它也能真正解锁它。 然后使用另一个内部接口在重新获取锁时恢复递归级别。

notify(n=1)

默认情况下,唤醒一个等待此条件的线程(如果有)。 如果调用此方法时调用线程尚未获取锁,则会引发 RuntimeError

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

如果至少有 n 个线程在等待,当前实现会准确唤醒 n 个线程。 但是,依赖这种行为是不安全的。 未来的优化实现可能偶尔会唤醒超过 n 个线程。

注意:被唤醒的线程实际上不会从它的 wait() 调用中返回,直到它可以重新获取锁。 由于 notify() 不释放锁,它的调用者应该。

notify_all()
notifyAll()

唤醒所有在此条件下等待的线程。 此方法的作用类似于 notify(),但唤醒所有等待线程而不是一个。 如果调用此方法时调用线程尚未获取锁,则会引发 RuntimeError

2.6 版更改: 添加 notify_all() 拼写。


16.2.5. 信号量对象

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

信号量管理一个内部计数器,该计数器由每个 acquire() 调用递减并由每个 release() 调用递增。 计数器永远不会低于零; 当 acquire() 发现它为零时,它会阻塞,等待其他线程调用 release()

class threading.Semaphore([value])

可选参数给出内部计数器的初始 ; 默认为 1。 如果给定的 小于 0,则提高 ValueError

acquire([blocking])

获取信号量。

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

blocking 设置为 true 的情况下调用时,执行与不带参数调用时相同的操作,并返回 true。

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

release()

释放信号量,将内部计数器加一。 当它在进入时为零并且另一个线程正在等待它再次变得大于零时,唤醒该线程。

16.2.5.1. 信号例子

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

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

一旦产生,工作线程在需要连接到服务器时调用信号量的获取和释放方法:

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

有界信号量的使用降低了导致信号量释放多于获取量的编程错误未被检测到的可能性。


16.2.6. 事件对象

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

事件对象管理一个内部标志,可以使用 set() 方法将其设置为 true,并使用 clear() 方法重置为 false。 wait() 方法阻塞,直到标志为真。

class threading.Event

内部标志最初为假。

is_set()
isSet()

当且仅当内部标志为真时才返回真。

2.6 版更改: 添加 is_set() 拼写。

set()

将内部标志设置为 true。 所有等待它变为真的线程都被唤醒。 一旦标志为真,调用 wait() 的线程根本不会阻塞。

clear()

将内部标志重置为 false。 随后,调用 wait() 的线程将阻塞,直到调用 set() 再次将内部标志设置为 true。

wait([timeout])

阻塞直到内部标志为真。 如果内部标志在进入时为真,则立即返回。 否则,阻塞直到另一个线程调用 set() 将标志设置为 true,或者直到发生可选超时。

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

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

2.7 版本变化: 以前,该方法总是返回 None


16.2.7. 定时器对象

这个类代表一个只有在经过一定时间后才应该运行的动作——一个计时器。 TimerThread 的子类,因此也可以作为创建自定义线程的示例。

定时器与线程一样,通过调用它们的 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={})
创建一个计时器,在 interval 秒过去后,使用参数 args 和关键字参数 kwargs 运行 function
cancel()
停止定时器,并取消定时器动作的执行。 这仅在计时器仍处于等待阶段时才有效。


16.2.8. 使用锁、条件和信号量和陈述

该模块提供的所有具有 acquire()release() 方法的对象都可以用作 with 语句的上下文管理器。 acquire()方法会在进入block时调用,release()方法会在block退出时调用。

目前,LockRLockConditionSemaphoreBoundedSemaphore对象可以用作 with 语句上下文管理器。 例如:

import threading

some_rlock = threading.RLock()

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

16.2.9. 在线程代码中导入

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

  • 首先,除了在主模块中,导入不应该产生产生新线程然后以任何方式等待该线程的副作用。 如果生成的线程直接或间接尝试导入模块,则不遵守此限制可能会导致死锁。
  • 其次,必须在解释器开始自行关闭之前完成所有导入尝试。 这可以通过仅从通过 threading 模块创建的非守护程序线程执行导入来最容易地实现。 守护线程和直接使用线程模块创建的线程将需要某种其他形式的同步,以确保它们在系统关闭开始后不会尝试导入。 不遵守此限制将导致解释器关闭期间的间歇性异常和崩溃(因为后期导入尝试访问不再处于有效状态的机器)。