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 密集型任务,线程仍然是一个合适的模型。

可用性: 不是 WASI。

此模块在 WebAssembly 上不起作用或不可用。有关详细信息,请参阅 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 可以使其复活。避免在自定义钩子完成后存储 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, GNU/kFreeBSD。

在 3.8 版本中添加。

在 3.13 版本中更改: 添加了对 GNU/kFreeBSD 的支持。

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

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

3.2 版本中新增。

此模块定义了许多类,这些类将在下面的章节中详细介绍。

此模块的设计大致基于 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 代码启动。虚拟线程对象的功能有限;它们始终被视为活跃和守护线程,并且无法被 join。它们永远不会被删除,因为无法检测到外部线程的终止。

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

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

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

targetrun() 方法要调用的可调用对象。默认为 None,表示不调用任何内容。

name 是线程名称。默认情况下,会构造一个唯一名称,形式为“Thread-N”,其中 N 是一个小的十进制数字,或者“Thread-N (target)”,其中“target”是 target.__name__ (如果指定了 target 参数)。

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() 会引发 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) 或不是 (False)。必须在调用 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

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

在 3.13 版本中更改: Lock 现在是一个类。在早期的 Python 中,Lock 是一个工厂函数,它返回底层私有锁类型的实例。

acquire(blocking=True, timeout=-1)

获取锁,阻塞或非阻塞。

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

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

当使用设置为正值的浮点数 *timeout* 参数调用时,最多会阻塞 *timeout* 指定的秒数,只要无法获取锁。 *timeout* 参数为 -1 指定无限等待。当 *blocking* 为 False 时,禁止指定 *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() 必须成对使用:每次 acquire 都必须在获取锁的线程中有一个 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)

获取信号量。

当不带参数调用时

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

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

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

当使用除 None 之外的 timeout 参数调用时,它将最多阻塞 timeout 秒。如果在此时间间隔内获取操作未成功完成,则返回 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,如果提供,是一个可调用对象,当线程被释放时,由其中一个线程调用。timeout 是默认的超时值,如果 wait() 方法没有指定超时值,则使用此值。

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 语句的上下文管理器。