python 多线程模块参考
threading.active_count() | 返回当前处于 active 状态的线程的数目 |
threading.current_thread() | 返回调用者当前的 Thread 对象 |
threading.get_ident() | 返回当前线程的“thread identifier”属性(3.3新增) |
threading.enumerate() | 返回当前处于 active 状态的线程组成的列表 |
threading.settrace(func) | 为所有从 threading 模块生成的线程添加 trace function |
threading.setprofile(func) | 为所有从 threading 模块生成的线程添加 profile function |
threading.stack_size([size]) | 返回创建线程时分配的栈空间大小,或通过参数进行设定 |
注:上面有个别函数是无法通过“from threading import *”导入的,必须通过“.”来访问。
本模块含有如下常量:
threading.TIMEOUT_MAX | 此常量限定 timeout 参数的最大值,超则OverflowError(3.2新增) |
本模块还含有如下类:
1. Thread-Local Data
Thread-Local data 用于存放“线程区分”的数据。要想使用它,只须创建一个 local 类实例(或子类)然后在它的属性里存储数据。不同线程访问该实例的属性值,是不同的。
>>> mydata = threading.local()
>>> mydata.x = 1
>>> type(mydata)
<class '_thread._local'>
2. Thread Objects
- Thread 类用来实现子线程。有两种方法来使用它:
- 给构造器传一个可调用对象(可以是函数,也可以是覆盖了 __call__() 方法的类实例)
- 子类化 Thread 类,并覆盖 run() 方法
- 即,除了 __init__() 和 run() 以外,不要覆盖此类的其他任何方法。
- 在创建了 Thread 对象后,通过调用它的 start() 方法,run() 方法的内容会在一个新线程里被执行。
- 一旦一个线程开始执行,那么他就处于“alive”的状态,除非 run() 方法执行完毕,或引发了一个未处理的异常。Thread 对象的 is_alive() 方法可以用来检测对象当前的状态。
- 线程A 可以调用线程B 的 join() 方法,调用后线程A 会被挂起,直到线程B 结束。
- 每个线程都有自己的名字。可以在调用构造器时通过 name=None 来设定,或者实例化后通过 name 属性来访问
- 线程可以被标记为“daemon thread”,这表示主程序可以不管他们死活。。。我是说,当一个程序里只剩下“daemon thread”没有结束时,程序就会直接退出。这个标记可以通过对象的 setDaemon() 方法来设定,但一定要在调用 start() 之前。对象的 daemon 属性和 isDaemon() 方法都可以用来检查它的标记。
- Python 程序的初始线程叫做“main thread”,当然他肯定不是“daemon thread”:
>>> threading.current_thread()
<_MainThread(MainThread, started 1808)>
>>> threading.current_thread().name
'MainThread'
>>> type(threading.current_thread())
<class 'threading._MainThread'>
- 值得一提的是,程序中有可能出现一种叫“dummy thread objects”的对象,他们一般在 threading 模块之外被创建,比如直接运行的 C 代码,“dummy thread objects”只有有限的功能;他们永远被认为是“alive”的,而且不能够被 join()。考虑到 Python 无法检测到他们到底处于什么状态,那么他们也就永远不会被删除。
class threading.Thread(group=None,target=None,name=None,args=(),kwargs={},*,daemon=None)
- 请只使用关键字参数调用本构造器
- group 应为 None,这参数是为将来可能出现的 ThreadGroup 类准备的(现在还没有实现)
- target 为将被 run() 方法调用的可调用对象。如果是 None,那就意味着什么也不做
- name 是本线程的名字,默认会分配一个形如“Thread-N”的名字,其中 N 是一个十进制数
- args 是给 target 准备的非关键字参数
- kwargs 是给 target 准备的关键字参数
- daemon 用来设定线程的 daemon 属性,如果使用默认值(None),将从当前进程中继承
- 如果 Thread 的子类重写了构造函数,那么一定要确保在子类构造函数的第一行先调用父类的构造函数(Thread.__init__())
Thread 类的属性和方法:
start()
- 开始线程的执行
- 本方法至多只能调用一次。它会在独立线程中执行 run() 方法
- 如果对一个对象多次调用本方法,会引发 RuntimeError 异常
run()
- 包含了线程实际执行的内容
- 本方法可以在子类中覆盖。否则默认调用传给构造器的 target 参数(和 args,kwargs 参数)
join(timeout=None)
- 被调用 join() 方法的线程会一直阻塞调用者的线程,直到自己结束(正常结束,或引发未处理异常),或超出 timeout 的时间
- timeout 参数是一个以秒为单位的浮点数。当给出 timeout 参数时,因为 join() 方法总是返回 None,你应该随即调用 is_alive() 方法,来判断子线程到底是终结了,还是超时了。
- 如果没有给出 timeout 参数,那么调用者的进程会一直阻塞到本进程结束
- 一个线程可以被 join() 多次
- 当调用 join() 方法可能引发死锁,或被调用者的进程还未 start() 时,都会引发一个 RuntimeError
name
- 用于鉴别进程的一个字符串,没啥特别的意义。不同进程的 name 可以相同,初始值由构造器给出
getName() / setName()
- 为后向兼容而保留的方法,现在请直接通过 name 属性来访问
ident
- 本线程的“thread identifier”,如果线程还未开始,则值为 None。这是一个非零的整数。当进程结束,另一个新的进程开始时,本 id 可能会被回收。本 id 即使在进程结束后仍然有效
is_alive()
- 返回本进程是否是 alive 状态
daemon
- 布尔值,标明本进程是否为“daemon thread”。一定要在 start() 被调用前设定,否则引发 RuntimeError。初始值继承自当前线程,因此从主线程创建的子线程默认都是 False,而从“daemon thread”创建的子线程默认都是 True
- 当只剩下“daemon thread”为 alive 状态时,整个程序就会退出
isDaemon() / setDaemon()
- 为后向兼容而保留的方法,现在请直接访问 daemon 属性
3. Lock Objects
- 原语锁(synchronization primitive,也叫同步数据结构锁、互斥锁、二值信号量)是一种对所有线程开放访问的同步原语。它是现今 Python 中最低级的同步原语,直接由 _thread 模块实现。
- 原语锁总是处于”locked”或者“unlocked”两种状态之一,初始默认为“unlocked”状态。它有两个基本方法:acquire() 和 release()。在“unlocked”状态下调用 acquire(),原语锁会进入“locked”状态并立即返回一个“True”;但如果是在“locked”状态下尝试调用 acquire(),调用者的线程会阻塞,直到该原语锁被其他线程释放,然后阻塞的 acquire() 立即将锁转换到“locked”状态并返回值。 release() 方法不能在“unlocked”状态下调用,否则会引发 RuntimeError 异常。
- 锁对象同样支持“上下文管理协议”。
- 假如有多个线程因为试图调用一个“locked”状态的锁的 acquire() 方法而被阻塞住,那么在该锁被释放后只会有一个线程能够成功获取到这个锁,至于是哪一个锁,这不受我们控制,而且没有规律。
- 两个方法的执行都是原语级的。
class threading.Lock
- 本类用于实现原语锁对象。当一个锁被某个线程获取到时,其它试图再获取本锁的线程都会阻塞,直到本锁被释放掉,任何线程都可以调用 release()方法。
- Python3.3 中,Lock 从一个工厂函数转换成了一个类
Lock 类包含的方法:
acquire(blocking=True,timeout=-1)
- 以阻塞或非阻塞的方式获取一个锁
- 当 blocking 参数为 True(默认)时,调用者的线程会阻塞直到锁转为“unlocked”状态,随即将之设为“locked”状态并返回 True
- 当 blocking 参数为 False 时,则不会阻塞。但如果 acquire() 失败(锁已经是 locked状态),则会立即返回一个 False,并继续执行线程
- 当 timeout 参数为一个正的浮点数时,若发生阻塞,则至多阻塞 timeout 秒;若为 –1 (默认值),则表示会一直阻塞下去。(注:模块内的 TIMEOUT_MAX 常量仅限制设定 timeout 参数时的最大值,并不会限制“无限阻塞”这种状况的执行)另外不允许当 blocking=False 时设置 timeout 参数。
- 本方法在获取成功时返回 True,失败则返回 False,比如说 timeout 超时
- 3.2 新增:timeout 参数
- 3.2 新增:现在 Lock.acquire() 可以被 POSIX 的信号中断
release()
- 释放一个锁,本方法可以在任何线程内调用,不限定获得锁的那个线程
- 当锁处于“locked”状态时,本方法将之重置为“unlocked”状态,并返回(None)。如果有其他线程在等待本锁解锁,则只有其中一个线程可以成功获取到本锁
- 若调用一个“unlocked”锁的本方法,则会引发一个 RuntimeError 异常
- 本方法无返回值(None)
4. RLock Objects
- 可重入锁是一种可以由同一个线程多次获取的同步原语。或者说,它使用“owning thread”和“recursion level”的概念(后面译作“绑定线程”和“递归等级”)来扩充原语锁的“locked/unlocked”状态。在“locked”状态下,可重入锁是绑定到某个线程的,“unlocked”状态下不绑定任何线程
- 调用锁的 acquire() 方法就可以将该锁锁住,一旦线程绑定到该锁上就立即返回。调用锁的 release() 方法可以解锁。acquire() / release() 调用是成对的,只有最终(最外层)的 release() 调用可以将锁重置到“unlocked”状态,并允许其他线程再次获取该锁
- 可重入锁同样支持“上下文管理协议”
class threading.RLock
- 本类用于实现可重入锁对象。可重入锁只能被获取到它的那个线程释放。一旦一个线程获取到了一个可重入锁,这个线程就可以再次获取这个锁,该线程获取了多少次,最后就得释放多少次
- 注意 RLock 其实是一个工厂函数,他总是返回一个当前平台下最高效的一个 RLock 版本
RLock 类包含的方法:
acquire(blocking=True,timeout=-1)
- 以阻塞或非阻塞的方式获取一个锁
- 无参数调用时:如果线程已经绑定到这个锁上,则递归等级 +1,然后立即返回。如果是非绑定的其他线程,则阻塞直到锁状态置为“unlocked”。当锁的状态变为“unlocked”时(没有绑定到任何一个线程),立即绑定该锁,将递归等级设为 1,然后返回。如果有多个线程在等待获取锁,则只会有一个能够成功获得
- 显式设定 blocking=True 时,和无参数的状况相同
- 显示设定 blocking=False 时,调用本方法的进程不会阻塞,但如果获取失败,则会返回一个 False。
- 当 timeout 参数为一个正的浮点数时,若发生阻塞,则至多阻塞 timeout 秒;若最后获取成功则返回 True,超时则返回 False
- 3.2 新增:timeout 参数
release()
- 用于释放一个锁,功能为将递归等级减 1 。当递归等级减到 0 时,锁就被置为“unlocked”状态(不被任何线程绑定)。这时如果有其他线程在等待获取这个锁,那么其中只能有一个能够成功。如果递归等级减 1 后仍不为零,则保持“locked”状态,和与原线程的绑定关系
- 本方法只能被绑定的线程调用,否则引发 RuntimeError 异常
- 本方法无返回值(None)
5. Condition Objects
- 条件变量(condition variable)总是和锁联用的;你可以亲自给它传递一个锁,或者默认情况下它也可以自动生成。手动传的状况一般发生在多个条件变量要共享同一个锁的时候,条件变量的锁可以看成它自己的一部分
- 条件变量支持上下文管理协议:with 语句会获取关联锁。acquire() 和 release() 方法也会调用关联锁的对应方法
- 条件变量的其他方法只能在已获取到锁的情况下调用。wait() 方法会释放锁,并阻塞直到其他线程通过 notify() 或 notify_all() 方法唤醒他。一旦被唤醒后,wait() 方法将从新获取锁并返回。
- notify() 和 notify_all() 方法用于唤醒正处于等待状态的线程。
- 注意:notify() 和 notify_all() 方法并不会释放锁;这即是说被唤醒的线程的 wait() 方法并不能立即返回,只能等到调用 notify() 方法的线程主动释放锁之后
- 使用条件变量的典型编程风格是通过锁去同步对某些公共状态的访问,关注某种状态变化的线程可以重复调用 wait() 方法直到观察到状态改变,而有机会改变某种状态的线程则可以在每次改变状态后就主动通过 notify() 方法去唤醒等待的线程一次,至于状态是否已经变更为等待线程需要的结果,那由等待线程自己去检查。
- 举个栗子,下面的代码是一个普通的带有无限缓冲容量的 生产者-消费者 环境:
#consume one item with cv: while not an_item_is_available(): cv.wait() get_ab_avilable_item() #produce one item with cv: make_an_item_available() cv.notify()
- 这里使用了 while 循环是因为 wait() 函数会在被唤醒后返回,而此时 an_item_is_available() 的值未必为 True。这种状况是多线程编程固有的。不过也可以使用 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 参数,只能使用 Lock 或 RLock 对象,而且它会被当做一个隐性锁使用。如果不传此参数,那么程序会自动隐性地创建一个 RLock 对象。
- Python 3.3 起,Condition 从工厂函数转变为类
Condition 类包含的方法:
acquire(*args)
- 本方法用于获取隐性锁(关联锁),它调用隐性锁的 acquire() 方法,并返回其所返回的值
release()
- 同上,本方法无返回值
wait(timeout=None)
- 等待通知或超时。如果线程没有获取到锁就调用了此方法,那么将引发 RuntimeError 异常
- 本方法会释放隐性锁,然后阻塞直到被其他线程的调用此条件变量的 notify() 或 notify_all() 唤醒,或超时。一旦被唤醒或超时,该线程将立即重新获取锁并返回
- timeout 参数是以秒为单位的浮点数
- 如果隐性锁是一个 RLock 对象,因为调用它的 release() 方法未必能够释放该锁,所以本方法会使用 RLock 对象的一个内部接口,该接口可以立即释放多重迭代的 RLock 锁。并且在需要重新获取锁的时候,也会使用一个类似的内部接口来恢复多重的迭代级别
- 本方法所阻塞的线程如果是被唤醒的,那么本方法会返回一个 True,如果是超时了,则返回 False
wait_for(predicate,timeout=None)
- 等待直到某个条件的值为 True。predicate 是一个返回可布尔化值的可调用对象。timeout 参数是可选的
- 本方法可能会多次调用 wait() 直到 predicate 为真,或超时。本方法的返回值为最后一次调用 predicate 的返回值,除非超时,超时的时候返回 False
- 不考虑 timeout 参数,调用此方法基本等价于:
while not predicate(): cv.wait()
- 因此,对于 wait() 的那一套规则在本方法内也适用:没有获取锁的线程不能调用本方法。
- 本方法为 3.2 新增
notify(n=1)
- 本方法默认用于唤醒处于等待本条件变量的线程。如果调用本方法的线程并没有获得锁,将引发 RuntimeError 异常
- 本方法至多可唤醒所有正在等待本条件变量的线程中的 n 个。如果调用时没有线程处于等待操作,那么本方法的调用是一个空操作
- 现在版本对本方法的实现为:在有足够多处于等待状态的线程的条件下,本方法将正好唤醒其中的 n 个,而不是像上一条中讲的“至多 n 个”。不过这种行为并不可靠。在将来,本方法很可能偶尔唤醒超过 n 条线程
notify_all()
- 唤醒正在等待本条件变量的所有线程。
6. Semaphore Objects
- Semaphore 是最古老的同步原语之一,由荷兰计算机科学家 Edsger W. Dijkstra 发明。(他最早使用名为 P() 和 V() 的函数对应 acquire() 和 release())
- Semaphore 在内部管理着一个计数器。调用 acquire() 会使这个计数器 -1,release() 则是 +1.计数器的值永远不会小于 0,当计数器到 0 时,再调用 acquire() 就会阻塞,直到其他线程来调用 release()
- Semaphore 也支持上下文管理协议
class threading.Semaphore(value=1)
- 本类用于实现 Semaphore 对象。可选参数 value 是计数器的初始值,默认为 1,不可为负,否则引发 ValueError
- Python 3.3 起,从一个工厂函数 改变为 类
Semaphore 类包含的方法:
acquire(blocking=True,timeout=None)
- 本方法用于获取 Semaphore
- 当使用默认参数调用本方法时:如果内部计数器的值大于零,将之减一,并返回;如果等于零,则阻塞,并等待其他线程调用 release() 方法以使计数器为正。这个过程有严格的互锁机制控制,以保证如果有多条线程正在等待解锁,release() 调用只会唤醒其中一条线程。唤醒哪一条是随机的。本方法返回 True,或无限阻塞
- 如果 blocking=False,则不阻塞,但若获取失败的话,返回 False
- 当设定了 timeout 参数时,最多阻塞 timeout 秒,如果超时,返回 False
- 3.2 新增:timeout 参数
release()
- 释放 Semaphore,给内部计数器 +1,可以唤醒处于等待状态的线程
class threading.BoundedSemaphore(value=1)
- 本类用于实现 bounded semaphore 对象。bounded semaphore 会检查内部计数器的值,并保证它不会大于初始值,如果超了,就引发一个 ValueError。多数情况下,semaphore 用于守护限制访问(但不限于 1)的资源,如果 semaphore 被 release() 过多次,这意味着存在 bug
- 3.3 新增:从工厂函数转变为类
举个栗子:
semaphore 常用于守护限制访问的资源,比如数据库。实际上在任何对访问有数量限制的情形下,你都应该使用 bounded semaphore 对象。在创建大量的访问线程前,你的主线程应该先初始化这个 semaphore。:
max_connection = 5
#...
pool_sema = BoundedSemaphore(value=max_connection)
with pool_sema:
with connectdb():
#...use connection...
7. Event Objects
- 这是一种最简单的线程间通信装置:一个线程操作一个 event,其他线程等待结果
- event 对象管理着一个内部 flag,set() 方法可以将之置为 True,clear() 方法则可以将之重置为 False。还有个 wait() 方法会阻塞线程直到 flag 为 True
class threading.Event
- 本类用于实现 Event 对象。flag 初始值为 False
- 3.3 新增:从工厂函数改为类
Event 类包含的方法:
is_set()
- 当且仅当 flag 为 True时,返回True
set()
- 将 flag 置为 True。所有等待的线程将被唤醒。在 flag 为 True 时调用 wait() 方法的线程不会被阻塞
clear()
- 将 flag 重置为 False。此后调用 wait() 方法的线程都会被阻塞,直到 flag 被 set() 置为 True
wait(timeout=None)
- 阻塞线程直到 flag 被置为 True。如果 flag 一开始就是 True,则本方法立即返回。否则,阻塞直到其他线程调用 set() 方法将 flag 置为 True,或超时
- 当给出 timeout 参数时,它应该是以秒为单位的浮点数
- 本方法当且仅当 flag 为 True 时返回 True,除非超时,超时返回 False
- 3.1 以前,本方法始终返回 None
8. Timer Objects
- Timer 是 Thread 类的子类,不过本类里内容的执行需要延迟一段时间
- Timer 和 Thread 一样,通过调用 start() 来开始,不过在延迟结束前都可以调用 cancel() 方法来取消。Timer 的延迟时间可能与用户指定的时间并不完全相同
- Timer 使用示例:
def hello(): print('Hello,world') t = Timer(30.0,hello) t.start()
class threading.Timer(interval,function,args=None,kwargs=None)
- 本类用于实现 Timer 对象,Timer 会在 interval 秒后开始执行 function。
- 3.3 新增:从工厂函数改为类
Timer 类包含的方法:
cancel()
- 终止计时器并取消内容的执行。本方法只可在计时器走完前调用
9. Barrier Objects
- 3.2 新增
- 本类为多条线程需要互相等待对方的状况提供了一种简单的同步原语。(注:这里的 barrier 可以想象成赛马或越野摩托时起点处的栅栏,比赛开始的同时所有选手的栅栏同时打开。)每一条线程调用 wait() 方法后表明自己已就位并进入阻塞状态,当所有的线程都调用了 wait() 方法时,他们将同时被释放。
- Barrier 对象可以重复使用
- 下面是一个简单的栗子:
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 条线程的 barrier。action 参数是一个在全部线程被释放时可被其中一条线程调用的可调用对象。timeout 参数是给 wait() 方法准备的,如果线程调用 wait() 方法时没有显式设定 timeout,那么这里的 timeout 将作为默认值被使用
Barrier 类包含的方法:
wait(timeout=None)
- 表示线程就位。如果提供了 timeout 参数,本方法里提供的 timeout 参数优先级较 barrier 类构造器里的高
- 本方法的返回值是一个 0 到 parties-1 之间的整数,每条线程都不一样。这个值可以用作挑选一条线程做些清扫工作,比如:
i = barrier.wait() if i == 0: #只有一条线程会输出下面的内容 print('passed the barrier')
- 如果在类构造器里提供了 action 参数,那么其中一条线程要在被释放前调用 action。假如 action 引发了异常,barrier 将被置为 broken 状态
- 如果调用本方法超时,那么 barrier 将被置为 broken 状态
- 如果在有线程已经在等待时,barrier 被置为 broken状态,或调用了 reset() 方法,那么将引发一个 BrokenBarrierError 异常
reset()
- 本方法将 barrier 置为初始状态,即 empty 状态。所有已经在等待的线程都会接收到 BrokenBarrierError 异常
- 注意当有其他处于 unknown 状态的线程时,调用此方法将可能获取到额外的访问。因此如果一个 barrier 进入了 broken 状态,最好是放弃他并新建一个 barrier,而不是调用本方法
abort()
- 将 barrier 置为 broken 状态。本方法将使所有正在等待或将要调用 wait() 方法的线程收到 BrokenBarrierError 异常。本方法的使用情景为,比如有一条线程需要 abort(),又不想给其他线程造成死锁的状态
- 或许设定 timeout 参数要比使用本方法更可靠
parites
- 将要使用本 barrier 的线程的数量
n_waiting
- 正在等待本 barrier 的线程的数量
broken
- 布尔值。如果本 barrier 为 broken 状态,则为 True
exception threading.BrokenBarrierError
- 本异常是 RuntimeError 的子类。当 barrier 被 reset() 或 broken 时引发
10. 在 with 语句 中使用 locks、condition 和 semaphore
本模块里的对象,只要有 acquire() 、release() 这对方法的,都支持上下文管理协议。其中 acquire() 会在 __enter__() 里调用,release() 会在 __quit__() 里调用。因此,下面的用法:
with some_lock:
#do sth.
等价于:
some_lock.acquire()
try:
#do_sth.
finally:
some_lock.release()