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和对象:

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

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

  • threading. Condition ( )

    • 返回新条件变量对象的工厂函数。条件变量允许一个或多个线程 await,直到被另一个线程通知为止。

See Condition Objects.

  • threading. current_thread ( )
  • threading. currentThread ( )
    • 返回与调用者的控制线程相对应的当前Thread对象。如果调用者的控制线程不是passthreading模块创建的,则返回Function有限的虚拟线程对象。

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

  • threading. enumerate ( )

    • 返回当前所有活动的Thread个对象的列表。该列表包括守护线程,由current_thread()创建的伪线程对象和主线程。它不包括终止的线程和尚未启动的线程。
  • threading. Event ( )

    • 返回新事件对象的工厂函数。事件 Management 一个标志,该标志可以passset()方法设置为 true,而可以passclear()方法重置为 false。 wait()方法将阻塞,直到该标志为 true。

See Event Objects.

  • 类别 threading. local
    • 表示线程本地数据的类。线程局部数据是其值是特定于线程的数据。要 Management 线程本地数据,只需创建local(或子类)的实例并在其上存储属性:
mydata = threading.local()
mydata.x = 1

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

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

2.4 版的新Function。

  • threading. Lock ( )
    • 返回新的原始锁定对象的工厂函数。一旦线程获取了它,随后try获取它就会阻塞,直到被释放为止。任何线程都可以释放它。

See Lock Objects.

  • threading. RLock ( )
    • 返回新的可重入锁定对象的工厂函数。可重入锁必须由获取它的线程释放。一旦线程获取了可重入锁,同一线程就可以再次获取它而不会阻塞;线程必须在每次获取它后释放一次。

See RLock Objects.

  • threading. Semaphore([])

    • 一个工厂函数,返回一个新的 signal 量对象。signal 量 Management 一个计数器,该计数器代表release()个呼叫的数量减去acquire()个呼叫的数量以及初始值。 acquire()方法在必要时会阻塞,直到可以返回而不会使计数器为负数为止。如果未给出,则* value *默认为 1.

See Semaphore Objects.

  • threading. BoundedSemaphore([* value *])

    • 一个工厂函数,它返回一个新的有界 signal 量对象。有界 signal 量会检查以确保其当前值不超过其初始值。如果是,则引发ValueError。在大多数情况下,signal 量用于保护容量有限的资源。如果 signal 量被释放太多次,则表明存在错误。如果未给出,则* value *默认为 1.
  • threading. Thread

    • 表示控制线程的类。可以以受限方式安全地将此类归为一类。

See Thread Objects.

  • threading. Timer

    • 经过指定时间间隔后执行Function的线程。

See Timer Objects.

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

2.3 版的新Function。

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

2.3 版的新Function。

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

2.5 版的新Function。

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

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

该模块的设计大致基于 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。它们永远不会被删除,因为不可能检测到外来线程的终止。

    • class * threading. Thread(* group = None target = None name = None args =() kwargs ={} *)
    • 始终应使用关键字参数来调用此构造函数。参数为:
  • group *应该为None;当实现ThreadGroup类时保留给以后的扩展。

  • target *是由run()方法调用的可调用对象。默认值为None,表示什么都不会被调用。

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

  • args *是目标调用的参数 Tuples。默认为()

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

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

  • start ( )
    • 启动线程的活动。

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

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

  • run ( )
    • 表示线程活动的方法。

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

  • join([超时])
    • await 线程终止。这将阻塞调用线程,直到被调用join()方法的线程终止(正常或pass未处理的异常终止),或者直到发生可选的超时。

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

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

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

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

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

2.6 版的新Function。

  • getName ( )

    • setName ( )
      • name的 2.6 之前的 API。
  • ident

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

2.6 版的新Function。

  • is_alive ( )
    • isAlive ( )
      • 返回线程是否处于活动状态。

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

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

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

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

2.6 版的新Function。

  • isDaemon ( )
    • setDaemon ( )
      • daemon的 2.6 之前的 API。

16.2.2. 锁定物件

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

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

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

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

  • Lock. acquire([* blocking *])
    • 获取锁定,阻止或非阻止。

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

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

  • Lock. release ( )
    • 释放锁。

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

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

没有返回值。

  • locked ( )
    • Return true if the lock is acquired.

16.2.3. RLock 对象

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

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

  • RLock. acquire([* blocking = 1 *])
    • 获取锁定,阻止或非阻止。

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

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

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

  • RLock. release ( )
    • 释放一个锁,递归级别递减。如果递减后为零,则将锁重置为解锁状态(不属于任何线程),并且如果其他任何线程被阻塞,await 锁被解锁,则允许其中一个 continue 进行。如果在递减之后递归级别仍然不为零,则锁保持锁定并由调用线程拥有。

仅在调用线程拥有锁时才调用此方法。如果在解锁时调用此方法,则会引发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 线程。

    • class * threading. Condition([* lock *])
    • 如果给定* lock *参数而不是None,则它必须是LockRLock对象,并且用作基础锁。否则,将创建一个新的RLock对象,并将其用作基础锁。
  • acquire(** args *)

    • 获取基础锁。该方法在基础锁上调用相应的方法;返回值就是该方法返回的值。
  • release ( )

    • 释放基础锁。该方法在基础锁上调用相应的方法;没有返回值。
  • wait([超时])

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

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

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

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

  • notify(* n = 1 *)
    • 默认情况下,唤醒一个线程 await 这种情况(如果有)。如果在调用此方法时调用线程未获取锁,则会引发RuntimeError

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

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

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

  • notify_all ( )
    • notifyAll ( )
      • 唤醒所有在这种情况下 await 的线程。此方法的作用类似于notify(),但是唤醒所有 await 的线程,而不是一个。如果在调用此方法时调用线程未获取锁,则会引发RuntimeError

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

16.2.5. signal 量对象

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

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

  • 类别 threading. Semaphore([])

    • 可选参数提供内部计数器的初始* value ;默认为1。如果给定的值*小于 0,则引发ValueError
  • acquire([* * blocking *])

    • 获取 signal 量。

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

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

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

  • release ( )
    • 释放 signal 量,使内部计数器增加一。当它在进入时为零并且另一个线程正在 await 它再次变得大于零时,唤醒该线程。

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。

  • 类别 threading. Event

    • 内部标志最初为 false。
  • is_set ( )

    • isSet ( )
      • 当且仅当内部标志为 true 时,才返回 true。

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

  • set ( )

    • 将内部标志设置为 true。唤醒所有 await 变为真的线程。一旦标记为 true,则调用wait()的线程将完全不会阻塞。
  • clear ( )

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

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

当存在 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
    • class * threading. Timer(* interval function args = [] kwargs ={} *)
    • 创建一个计时器,该计时器将在* interval 秒过去之后,使用参数 args 和关键字参数 kwargs 运行 function *。
  • cancel ( )

    • 停止计时器,并取消执行计时器的操作。仅当计时器仍处于 await 阶段时,此Function才起作用。

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. 导入线程代码

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

  • 首先,除了在主模块中以外,导入不应具有产生新线程然后以任何方式 await 该线程的副作用。如果衍生的线程直接或间接try导入模块,则不遵守此限制可能导致死锁。

  • 其次,必须在解释程序开始关闭自身之前完成所有导入try。pass仅从pass线程模块创建的非守护程序线程执行导入,可以最轻松地实现这一点。守护程序线程和直接由线程模块创建的线程将需要某种其他形式的同步,以确保它们在系统关闭开始后不会try导入。不遵守此限制将导致间歇性异常并在解释器关闭期间崩溃(因为较晚的导入会try访问不再处于有效状态的机器)。