threading — 基于线程的并行

源代码: Lib/threading.py


此模块在较低级别的 _thread 模块之上构建了更高级别的线程接口。

版本 3.7 中的变化: 此模块以前是可选的,现在始终可用。

参见

concurrent.futures.ThreadPoolExecutor 提供了一个更高级别的接口,可以将任务推送到后台线程而不阻塞调用线程的执行,同时仍然能够在需要时检索其结果。

queue 提供了一个线程安全的接口,用于在运行的线程之间交换数据。

asyncio 提供了一种替代方法,可以在不需要使用多个操作系统线程的情况下实现任务级并发。

注意

在 Python 2.x 系列中,此模块包含一些方法和函数的 camelCase 命名。从 Python 3.10 开始,这些名称已被弃用,但为了与 Python 2.5 及更低版本兼容,仍然支持它们。

CPython 实现细节: 在 CPython 中,由于 全局解释器锁 的存在,一次只能有一个线程执行 Python 代码(即使某些面向性能的库可能会克服此限制)。如果您希望您的应用程序更好地利用多核机器的计算资源,建议您使用 multiprocessingconcurrent.futures.ProcessPoolExecutor。但是,如果您想同时运行多个 I/O 密集型任务,线程仍然是一个合适的模型。

可用性:不可用于 Emscripten,不可用于 WASI。

此模块在 WebAssembly 平台 wasm32-emscriptenwasm32-wasi 上不可用或无法工作。有关更多信息,请参阅 WebAssembly 平台

此模块定义了以下函数

threading.active_count()

返回当前处于活动状态的 Thread 对象的数量。返回的计数等于 enumerate() 返回的列表的长度。

函数 activeCount 是此函数的已弃用别名。

threading.current_thread()

返回当前的 Thread 对象,对应于调用者的控制线程。如果调用者的控制线程不是通过 threading 模块创建的,则会返回一个功能有限的虚拟线程对象。

函数 currentThread 是此函数的已弃用别名。

threading.excepthook(args, /)

处理由 Thread.run() 引发的未捕获异常。

args 参数具有以下属性

  • exc_type:异常类型。

  • exc_value:异常值,可以是 None

  • exc_traceback:异常回溯,可以是 None

  • thread:引发异常的线程,可以是 None

如果 exc_typeSystemExit,则静默忽略该异常。否则,将在 sys.stderr 上打印该异常。

如果此函数引发异常,则会调用 sys.excepthook() 来处理它。

可以覆盖 threading.excepthook() 以控制如何处理由 Thread.run() 引发的未捕获异常。

使用自定义钩子存储 exc_value 可能会创建循环引用。当不再需要该异常时,应显式清除它以打破循环引用。

如果 thread 被设置为正在被终结的对象,则使用自定义钩子存储它可能会使其复活。避免在自定义钩子完成后存储 thread 以避免对象复活。

参见

sys.excepthook() 处理未捕获的异常。

3.8 版新增。

threading.__excepthook__

保存 threading.excepthook() 的原始值。保存它是为了在它们碰巧被损坏或替代的对象替换时可以恢复原始值。

3.10 版新增。

threading.get_ident()

返回当前线程的“线程标识符”。这是一个非零整数。它的值没有直接含义;它旨在作为一种魔术cookie,例如用于索引线程特定数据的字典。当一个线程退出并创建另一个线程时,线程标识符可能会被回收。

3.3 版新增。

threading.get_native_id()

返回由内核分配给当前线程的原生整数线程 ID。这是一个非负整数。它的值可用于在系统范围内唯一标识此特定线程(直到线程终止,之后该值可能会被操作系统回收)。

可用性:Windows、FreeBSD、Linux、macOS、OpenBSD、NetBSD、AIX、DragonFlyBSD。

3.8 版新增。

threading.enumerate()

返回当前处于活动状态的所有 Thread 对象的列表。该列表包括守护线程和由 current_thread() 创建的虚拟线程对象。它不包括已终止的线程和尚未启动的线程。但是,主线程始终是结果的一部分,即使在终止时也是如此。

threading.main_thread()

返回主 Thread 对象。在正常情况下,主线程是启动 Python 解释器的线程。

版本 3.4 中的新功能。

threading.settrace(func)

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

threading.settrace_all_threads(func)

为从 threading 模块启动的所有线程以及当前正在执行的所有 Python 线程设置跟踪函数。

在调用每个线程的 run() 方法之前,会将 func 传递给 sys.settrace()

版本 3.12 中的新功能。

threading.gettrace()

获取由 settrace() 设置的跟踪函数。

3.10 版新增。

threading.setprofile(func)

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

threading.setprofile_all_threads(func)

为从 threading 模块启动的所有线程以及当前正在执行的所有 Python 线程设置分析函数。

在调用每个线程的 run() 方法之前,会将 func 传递给 sys.setprofile()

版本 3.12 中的新功能。

threading.getprofile()

获取由 setprofile() 设置的分析器函数。

3.10 版新增。

threading.stack_size([size])

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

可用性:Windows、pthreads。

支持 POSIX 线程的 Unix 平台。

此模块还定义了以下常量

threading.TIMEOUT_MAX

阻塞函数(Lock.acquire()RLock.acquire()Condition.wait() 等)的 timeout 参数允许的最大值。指定大于此值的超时将引发 OverflowError

版本 3.2 中的新功能。

此模块定义了许多类,将在以下部分中详细介绍。

此模块的设计 loosely 基于 Java 的线程模型。但是,Java 将锁和条件变量作为每个对象的基本行为,而在 Python 中它们是独立的对象。Python 的 Thread 类支持 Java 的 Thread 类的一部分行为;目前,没有优先级,没有线程组,并且线程不能被销毁、停止、挂起、恢复或中断。Java 的 Thread 类的静态方法(如果已实现)将映射到模块级函数。

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

线程局部数据

线程局部数据是其值特定于线程的数据。要管理线程局部数据,只需创建 local(或子类)的实例并在其上存储属性

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

对于不同的线程,实例的值将不同。

class threading.local

表示线程本地数据的类。

有关更多详细信息和大量示例,请参阅 _threading_local 模块的文档字符串:Lib/_threading_local.py

线程对象

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

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

线程活动启动后,该线程被视为“活动”状态。当其 run() 方法终止时(正常终止或引发未处理的异常),它将停止活动。 is_alive() 方法测试线程是否处于活动状态。

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

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

如果 run() 方法引发异常,则会调用 threading.excepthook() 来处理它。默认情况下,threading.excepthook() 会静默忽略 SystemExit

可以将线程标记为“守护线程”。此标志的意义在于,当只剩下守护线程时,整个 Python 程序将退出。初始值从创建线程继承。可以通过 daemon 属性或 daemon 构造函数参数设置该标志。

注意

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

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

有可能创建“虚拟线程对象”。这些线程对象对应于“外部线程”,它们是在 threading 模块外部启动的控制线程,例如直接从 C 代码启动。虚拟线程对象的功能有限;它们始终被视为活动状态和守护线程,并且不能 连接。它们永远不会被删除,因为无法检测外部线程的终止。

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

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

group 应为 None;保留以供将来实现 ThreadGroup 类时使用。

target 是由 run() 方法调用的可调用对象。默认为 None,表示不调用任何内容。

name 是线程名称。默认情况下,将构建一个唯一名称,格式为“Thread-N”,其中 N 是一个小十进制数,如果指定了 target 参数,则为“Thread-N (target)”,其中“target”为 target.__name__

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

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

如果不是 None,则 daemon 显式设置线程是否为守护线程。如果为 None(默认值),则守护线程属性将从当前线程继承。

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

在 3.3 版更改: 添加了 daemon 参数。

在 3.10 版更改: 如果省略 name 参数,则使用 target 名称。

start()

启动线程的活动。

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

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

run()

表示线程活动的方法。

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

使用列表或元组作为传递给 Threadargs 参数可以达到相同的效果。

示例

>>> from threading import Thread
>>> t = Thread(target=print, args=[1])
>>> t.run()
1
>>> t = Thread(target=print, args=(1,))
>>> t.run()
1
join(timeout=None)

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

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

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

一个线程可以被多次 join。

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

name

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

getName()
setName()

name 的已弃用 getter/setter API;请直接将其用作属性。

自版本 3.10 起已弃用。

ident

此线程的“线程标识符”,如果线程尚未启动,则为 None。这是一个非零整数。请参阅 get_ident() 函数。当线程退出并创建另一个线程时,线程标识符可能会被回收。即使在线程退出后,标识符仍然可用。

native_id

此线程的线程 ID (TID),由操作系统(内核)分配。这是一个非负整数,如果线程尚未启动,则为 None。请参阅 get_native_id() 函数。此值可用于在系统范围内唯一标识此特定线程(直到线程终止,之后该值可能会被操作系统回收)。

注意

与进程 ID 类似,线程 ID 仅在线程创建到线程终止这段时间内有效(保证系统范围内唯一)。

可用性:Windows、FreeBSD、Linux、macOS、OpenBSD、NetBSD、AIX、DragonFlyBSD。

3.8 版新增。

is_alive()

返回线程是否处于活动状态。

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

daemon

一个布尔值,指示此线程是否是守护线程(True)。这必须在调用 start() 之前设置,否则会引发 RuntimeError。它的初始值继承自创建线程;主线程不是守护线程,因此在主线程中创建的所有线程默认为 daemon = False

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

isDaemon()
setDaemon()

daemon 的已弃用 getter/setter API;请直接将其用作属性。

自版本 3.10 起已弃用。

锁对象

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

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

锁还支持 上下文管理协议

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

所有方法都以原子方式执行。

class threading.Lock

实现原始锁对象的类。一旦一个线程获取了锁,后续获取锁的尝试将被阻塞,直到锁被释放;任何线程都可以释放锁。

请注意,Lock 实际上是一个工厂函数,它返回平台支持的最高效的具体 Lock 类实例。

acquire(blocking=True, timeout=-1)

获取锁,阻塞或非阻塞。

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

当使用将 blocking 参数设置为 False 时,不会阻塞。如果将 blocking 设置为 True 的调用会被阻塞,则立即返回 False;否则,将锁设置为锁定状态并返回 True

当使用将浮点型 timeout 参数设置为正值时,最多阻塞 timeout 指定的秒数,并且只要无法获取锁就一直阻塞。timeout 参数为 -1 表示无限期等待。当 blockingFalse 时,禁止指定 timeout

如果成功获取锁,则返回值为 True,否则为 False(例如,如果 timeout 已过期)。

版本 3.2 中的变化: 新增了 timeout 参数。

版本 3.2 中的变化: 如果底层线程实现支持,现在可以通过 POSIX 上的信号中断锁获取。

release()

释放锁。这可以从任何线程调用,而不仅仅是从获取锁的线程调用。

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

当在未锁定的锁上调用时,会引发 RuntimeError

没有返回值。

locked()

如果锁已被获取,则返回 True

RLock 对象

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

线程调用锁的 acquire() 方法来锁定它,并调用其 release() 方法来解锁它。

注意

可重入锁支持 上下文管理协议,因此建议使用 with 语句,而不是手动调用 acquire()release() 来处理代码块的锁获取和释放。

与 Lock 的 acquire()/release() 不同,RLock 的 acquire()/release() 调用对可以嵌套。只有最后的 release()(最外层对的 release())才会将锁重置为解锁状态,并允许另一个在 acquire() 中阻塞的线程继续执行。

acquire()/release() 必须成对使用:每次获取都必须在获取锁的线程中进行释放。如果调用 release 的次数少于获取锁的次数,则可能导致死锁。

class threading.RLock

此类实现可重入锁对象。可重入锁必须由获取它的线程释放。一旦一个线程获取了可重入锁,则同一个线程可以再次获取它而不会阻塞;该线程必须针对每次获取锁的操作释放一次锁。

请注意,RLock 实际上是一个工厂函数,它返回平台支持的最高效的具体 RLock 类实例。

acquire(blocking=True, timeout=-1)

获取锁,阻塞或非阻塞。

参见

将 RLock 用作上下文管理器

只要可行,建议使用此方法,而不是手动调用 acquire()release()

当使用将 blocking 参数设置为 True(默认值)时

  • 如果没有线程拥有锁,则获取锁并立即返回。

  • 如果另一个线程拥有锁,则阻塞直到我们能够获取锁,或者如果 timeout 设置为正浮点数,则阻塞到超时。

  • 如果同一个线程拥有锁,则再次获取锁,并立即返回。这是 LockRLock 之间的区别;Lock 处理这种情况的方式与前一种情况相同,即阻塞直到可以获取锁。

当使用将 blocking 参数设置为 False

  • 如果没有线程拥有锁,则获取锁并立即返回。

  • 如果另一个线程拥有锁,则立即返回。

  • 如果同一个线程拥有锁,则再次获取锁并立即返回。

在所有情况下,如果线程能够获取锁,则返回 True。如果线程无法获取锁(即,如果没有阻塞或已达到超时时间),则返回 False

如果多次调用,未能调用 release() 的次数与获取锁的次数相同可能会导致死锁。请考虑将 RLock 用作上下文管理器,而不是直接调用 acquire/release。

版本 3.2 中的变化: 新增了 timeout 参数。

release()

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

仅当调用线程拥有锁时才调用此方法。如果在未获取锁的情况下调用此方法,则会引发 RuntimeError

没有返回值。

条件对象

条件变量始终与某种锁相关联;这可以传入,也可以默认创建一个。当多个条件变量必须共享同一个锁时,传入一个锁非常有用。锁是条件对象的一部分:您不必单独跟踪它。

条件变量遵循 上下文管理协议:使用 with 语句在封闭块的持续时间内获取关联的锁。acquire()release() 方法也调用关联锁的相应方法。

其他方法必须在持有关联锁的情况下调用。wait() 方法释放锁,然后阻塞,直到另一个线程通过调用 notify()notify_all() 唤醒它。一旦被唤醒,wait() 会重新获取锁并返回。也可以指定超时。

notify() 方法唤醒等待条件变量的线程之一(如果有)。notify_all() 方法唤醒所有等待条件变量的线程。

注意:notify()notify_all() 方法不会释放锁;这意味着被唤醒的线程不会立即从它们的 wait() 调用中返回,而只会在调用 notify()notify_all() 的线程最终放弃锁的所有权时才会返回。

使用条件变量的典型编程风格是使用锁来同步对某些共享状态的访问;对特定状态更改感兴趣的线程重复调用 wait(),直到看到所需的状态,而修改状态的线程在更改状态时调用 notify()notify_all(),这样它可能成为其中一个等待者所需的状态。例如,以下代码是具有无限缓冲区容量的通用生产者-消费者情况

# Consume one item
with cv:
    while not an_item_is_available():
        cv.wait()
    get_an_available_item()

# Produce one item
with cv:
    make_an_item_available()
    cv.notify()

检查应用程序条件的 while 循环是必要的,因为 wait() 可以在任意长时间后返回,并且导致 notify() 调用的条件可能不再成立。这是多线程编程所固有的。wait_for() 方法可用于自动执行条件检查,并简化超时计算

# Consume an item
with cv:
    cv.wait_for(an_item_is_available)
    get_an_available_item()

要在 notify()notify_all() 之间进行选择,请考虑一个状态更改是否只对一个或多个等待线程有意义。例如,在典型的生产者-消费者情况下,向缓冲区添加一个项目只需要唤醒一个消费者线程。

class threading.Condition(lock=None)

此类实现条件变量对象。条件变量允许一个或多个线程等待,直到它们被另一个线程通知。

如果给出了 lock 参数且不为 None,则它必须是 LockRLock 对象,并且它被用作底层锁。否则,将创建一个新的 RLock 对象并将其用作底层锁。

在 3.3 版更改: 从工厂函数更改为类。

acquire(*args)

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

release()

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

wait(timeout=None)

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

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

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

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

返回值为 True,除非给定的 timeout 已过期,在这种情况下,返回值为 False

在 3.2 版更改: 之前,该方法始终返回 None

wait_for(predicate, timeout=None)

等待,直到条件评估为真。predicate 应该是一个可调用对象,其结果将被解释为布尔值。可以提供 timeout,给出最长等待时间。

此实用程序方法可以重复调用 wait(),直到谓词满足或发生超时。返回值是谓词的最后一个返回值,如果该方法超时,则返回值将评估为 False

忽略超时功能,调用此方法大致相当于编写

while not predicate():
    cv.wait()

因此,与 wait() 相同的规则适用:调用时必须持有锁,并在返回时重新获取锁。在持有锁的情况下评估谓词。

版本 3.2 中的新功能。

notify(n=1)

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

此方法最多唤醒 n 个等待条件变量的线程;如果没有线程在等待,则此方法无效。

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

注意:被唤醒的线程实际上要等到它能够重新获取锁后才会从其 wait() 调用返回。由于 notify() 不会释放锁,因此其调用者应该释放锁。

notify_all()

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

方法 notifyAll 是此方法的已弃用别名。

信号量对象

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

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

信号量还支持 上下文管理协议

class threading.Semaphore(value=1)

此类实现信号量对象。信号量管理一个原子计数器,该计数器表示 release() 调用次数减去 acquire() 调用次数,再加上初始值。acquire() 方法在必要时阻塞,直到它可以在不使计数器变为负数的情况下返回。如果未给出,则 value 默认为 1。

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

在 3.3 版更改: 从工厂函数更改为类。

acquire(blocking=True, timeout=None)

获取信号量。

在不带参数的情况下调用时

  • 如果进入时内部计数器大于零,则将其减 1 并立即返回 True

  • 如果内部计数器在进入时为零,则阻塞直到被调用 release() 唤醒。一旦被唤醒(并且计数器大于 0),将计数器减 1 并返回 True。每次调用 release() 将唤醒一个线程。线程被唤醒的顺序不应该被依赖。

当使用 blocking 设置为 False 调用时,不要阻塞。如果一个没有参数的调用会被阻塞,则立即返回 False;否则,执行与没有参数调用时相同的操作,并返回 True

当使用非 Nonetimeout 调用时,它将最多阻塞 timeout 秒。如果 acquire 在该时间间隔内没有成功完成,则返回 False。否则返回 True

版本 3.2 中的变化: 新增了 timeout 参数。

release(n=1)

释放一个信号量,将内部计数器增加 n。当它在进入时为零并且其他线程正在等待它再次大于零时,唤醒这些线程中的 n 个。

在 3.9 版更改: 添加了 n 参数以一次释放多个等待线程。

class threading.BoundedSemaphore(value=1)

实现有界信号量对象的类。有界信号量会检查以确保其当前值不超过其初始值。如果超过,则会引发 ValueError。在大多数情况下,信号量用于保护容量有限的资源。如果信号量被释放的次数过多,则表明存在错误。如果没有给出,则 value 默认为 1。

在 3.3 版更改: 从工厂函数更改为类。

Semaphore 示例

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

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

一旦生成,工作线程在需要连接到服务器时调用信号量的 acquire 和 release 方法

with pool_sema:
    conn = connectdb()
    try:
        # ... use connection ...
    finally:
        conn.close()

使用有界信号量可以减少编程错误导致信号量释放次数超过获取次数而未被发现的可能性。

事件对象

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

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

class threading.Event

实现事件对象的类。事件管理一个标志,该标志可以使用 set() 方法设置为 true,并使用 clear() 方法重置为 false。 wait() 方法会阻塞,直到标志为 true。该标志最初为 false。

在 3.3 版更改: 从工厂函数更改为类。

is_set()

当且仅当内部标志为 true 时返回 True

方法 isSet 是此方法的已弃用别名。

set()

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

clear()

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

wait(timeout=None)

只要内部标志为 false 并且超时(如果给出)尚未过期,就一直阻塞。返回值表示此阻塞方法返回的原因;如果因为内部标志设置为 true 而返回,则为 True,如果给定了超时并且内部标志在给定的等待时间内没有变为 true,则为 False

当存在 timeout 参数并且不为 None 时,它应该是一个浮点数,指定操作的超时时间(以秒为单位),或者以秒为单位的小数。

在 3.1 版更改: 以前,该方法始终返回 None

计时器对象

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

与线程一样,计时器通过调用其 Timer.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=None, kwargs=None)

创建一个定时器,该定时器将在 interval 秒后使用参数 args 和关键字参数 kwargs 运行 function。如果 argsNone(默认值),则将使用空列表。如果 kwargsNone(默认值),则将使用空字典。

在 3.3 版更改: 从工厂函数更改为类。

cancel()

停止计时器,并取消执行计时器的操作。这仅在计时器仍处于等待阶段时有效。

屏障对象

版本 3.2 中的新功能。

此类提供了一个简单的同步原语,供需要相互等待的固定数量的线程使用。每个线程都尝试通过调用 wait() 方法来通过屏障,并且将阻塞,直到所有线程都进行了 wait() 调用。此时,线程将同时释放。

对于相同数量的线程,可以多次重复使用该屏障。

例如,以下是一种同步客户端和服务器线程的简单方法

b = Barrier(2, timeout=5)

def server():
    start_server()
    b.wait()
    while True:
        connection = accept_connection()
        process_server_connection(connection)

def client():
    b.wait()
    while True:
        connection = make_connection()
        process_client_connection(connection)
class threading.Barrier(parties, action=None, timeout=None)

parties 个线程创建屏障对象。action(如果提供)是一个可调用对象,当线程被释放时,其中一个线程将调用它。timeoutwait() 方法未指定超时值时的默认超时值。

wait(timeout=None)

通过屏障。当屏障的所有参与线程都调用此函数时,它们将同时释放。如果提供了 timeout,则优先使用它,而不是使用提供给类构造函数的任何值。

返回值是 0 到 parties – 1 范围内的整数,每个线程的值都不同。这可以用于选择一个线程来执行一些特殊的内务处理,例如

i = barrier.wait()
if i == 0:
    # Only one thread needs to print this
    print("passed the barrier")

如果为构造函数提供了 action,则其中一个线程将在释放之前调用它。如果此调用引发错误,则屏障将进入损坏状态。

如果调用超时,则屏障将进入损坏状态。

如果屏障在某个线程等待时被破坏或重置,则此方法可能会引发 BrokenBarrierError 异常。

reset()

将屏障恢复为默认的空状态。任何等待它的线程都将收到 BrokenBarrierError 异常。

请注意,如果存在其他状态未知的线程,则使用此函数可能需要一些外部同步。如果屏障被破坏,最好将其保留并创建一个新的屏障。

abort()

将屏障置于损坏状态。这会导致对 wait() 的任何活动或将来调用失败,并出现 BrokenBarrierError。例如,如果其中一个线程需要中止,请使用此方法以避免应用程序死锁。

最好是简单地创建一个具有合理 timeout 值的屏障,以自动防止其中一个线程出错。

parties

通过屏障所需的线程数。

n_waiting

当前在屏障中等待的线程数。

broken

一个布尔值,如果屏障处于损坏状态,则为 True

exception threading.BrokenBarrierError

Barrier 对象被重置或破坏时,将引发此异常,它是 RuntimeError 的子类。

with 语句中使用锁、条件和信号量

此模块提供的所有具有 acquirerelease 方法的对象都可以用作 with 语句的上下文管理器。进入代码块时将调用 acquire 方法,退出代码块时将调用 release 方法。因此,以下代码段

with some_lock:
    # do something...

等价于

some_lock.acquire()
try:
    # do something...
finally:
    some_lock.release()

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