1 queue 模块
queue 模块实现了多生产者、多消费者队列。可以方便在不同的线程之间传递信息。
1.1 Queue 类
模块中实现了三种类型的 queue ,即 FIFO 队列, LIFO 队列和 Priority 队列。分别由类 queue.Queue , queue.LifoQueue 和 queue.PriorityQueue 实现。各个类在定义对象时,可以指定队列的最大长度,无指定的话为无限大。
1.2 Queue 类对象的操作
名称 | 语义 |
qsize() | 用于返回队列当前的大小 |
empty() | 返回队列是否为空 |
put(item [,block [,timeout]] | 向队列中插入一条记录。默认 block = True, timeout = None ,操作会在队列满时阻塞直到可以插入后返回,如果 timeout 为正,则在队列满时阻塞 timout 后抛出 Full 异常。如果 block = False ,则在队列满时立即返回并抛出 Full 异常。 = put_nowait(item) |
get([block [,timeout]] | 从队列中删除一条记录并将其返回。默认 block = True, timeout = None ,操作会在队列空时阻塞直到可以从中取出一条记录,如果 timeout 为正,则阻塞时间超时后,抛出 Empty 异常。如果 block=False ,则在队列为空时立即返回并抛出 Empty 异 常。 =get_nowait() |
task_down() | 通知上一个队列操作已经完成。消费者线程在 get() 条目后,在完成相应工作后,需要调用 task_done() 以告知队列处理任务已经完成。如果 task_down() 调用次数超过队列中元素个数,则抛出 ValueError 异常。 |
join() | 阻塞一直到队列中所有 items 都被 get() 并处理。 |
2 高级线程操作 threading 模块
2.1 模块的方法
名称 | 语义 |
active_count() | 返回 Thread 对象的个数 |
Condition() | 返回一个条件变量对象 |
current_thread() | 返回当前线程对象 |
enumerate() | 返回当前活动线程 list |
Event() | 返回一个事件对象 |
lock() | 返回一个锁的对象 |
Rlock() | 返回一个可重入的锁对象 |
Semaphore(val=1) | 返回一个信号量的对象 |
settrace(func) | 为所有生成线程设置跟踪函数, func 会传递给 sys.settrace() |
setprofile(func) | 为所有生成线程设置计量函数, func 会传递给 sys.setprofile() |
stack_size(size) | 设置线程的栈大小,全局有效 |
2.2 Thread 类对象的方法
名称 | 语义 |
Thread(group=none,target,name,arg,dicarg) | 生成一个线程,执行例程为 target ,默认 target=None ,此时应由用户继承 Thread 类,并重载 run() ,派生类务必调用基类 __init__ |
start() | 启动线程 |
run() | 线程运行例程 |
getName()/ setName() | 获得 / 设置线程名称,可直接访问 name 成员,名称具有 Thread_N 的形式 ,N 为自然数 |
is_alive() | 线程是否存活 |
setDaemon(Ture) | 将线程设置为后台运行,可访问 daemon 成员 |
2.3 Lock 类对象的方法
名称 | 语义 |
acquire(blocking=True) | 获取锁,如果 blocking=False ,则类似 try_acquire() |
release() | 释放锁 |
2.4 RLock 类对象
可重入的锁是指线程可以多次锁住它的锁。它使用属主线程和迭代层次的概念以及已锁和未锁状态。当一个线程已经拥有一个锁时,再次调用 acquire() 将使迭代层次增加 1 ,并立即返回。但如果其他线程锁住了锁,则将导致阻塞。
2.5 Condition 对象
名称 | 语义 |
Condition(lock=None) | 构造一个条件变量,不使用外部锁进行关联。 |
acquire(*args) | 锁住条件变量关联的内部锁 |
release() | 释放条件变量关联的内部锁 |
wait(timeout=None) | 等待条件变量上事件,等待 notifiy 或超时 |
notify() | 唤醒等待在条件变量上的线程 |
notify_all() | 广播事件 |
2.6 Semaphore 类对象
名称 | 语义 |
Semaphore(value=1) | 构造一个信号量,默认并发度为 1 |
BoundedSemaphore(value=1) |
|
acquire(blocking=True) | P 操作,如果 blocking=False ,则类似 try_acquire() |
release() | V 操作 |
2.7 Event 类对象
名称 | 语义 |
Events() | 声明一个事件对象,内部有一个 flag |
is_set() | flag 是否设置 |
set() |
|
clear() |
|
wait(timeout=None) | 阻塞直到 flag 变为 True |
可以用于在线程间进行简单的通信。
2.8 Timer 类
名称 | 语义 |
Timer(interval,func,args=[],kwarg={}) | 声明一个线程每隔 interval 就执行 func , |
cancel() | 停止 Timer 对象 |
注 1 :由于锁,条件变量,信号量都声明了 acquire() 和 release() ,因此可以在 with 上下文管理语句中使用。
注 2 :如果系统没有提供 threading 模块,需要导入 dummy_threading 模块提供相应的接口
3 低级线程操作 _thread
语句 | 语义 |
start_new_thread(func,args[,kwargs] | 启动一个线程 |
interrupt_main() | 向主线程发送 key_interupt 异常 |
exit() | 发送 SystemExit 异常 |
allocate_lock() | 返回新的锁对象 |
lock.acquire() |
|
lock.release() |
|
lock.locked() |
|
注:如果系统没有提供 _thread 库,可以加载 dummy_thread 迭代
4 Python 多线程程序示例
5 你需要 Python 多线程吗?
5.1 GIL 全局解释器锁
全局解释器锁 (Global Interpretor Lock) 说明 Python 解释器并不是线程安全的。当前线程必须持有全局锁,以便对 Python 对象进行安全地访问。因为只有一个线程可以获得 Python 对象 /C API ,所以解释器每经过 100 个字节码的指令,就有规律地释放和重新获得锁。解释器对线程切换进行检查的频率可以通过 sys.setcheckinterval()
函数来进行控制。
需要说明的是,因为 GIL , CPU 受限的应用程序将无法从线程的使用中受益。使用 Python 时,建议使用进程,或者混合创建进程和线程。
5.2 Python 多线程的效率
由于 GIL 的存在, Python 并不是在多线程之间对共享数据进行加锁,而是采用了宏锁,一次只有一个线程运行于解释器中,多个线程只能在各自的时间片中访问解释器,并不能利用多核的优势,因为在系统看来,解释器只有一个进程。
在上面的例子中,由于工作是 CPU 密集型的,因此左边的线性工作更加快,因为 GIL 的存在,两个线程切换工作需要消耗时间。
下面来测试一下对于计算型和I/O型的操作,Python单个线程与多线程间的时间差异来看我们上面的分析是否正确:
代码mythread.py
测量代码:
结果:
[20.63806740800856, 20.339483649458241, 20.328089463884382]
[20.958624909031741, 21.177667552719697, 21.003685663960084]
[4.9799982958727993, 4.9634425604371497, 4.9663161100083961]
[4.9650368971475434, 4.9747188539325542, 6.4271900478971489]
6 相关资料
GIL介绍 http://www.pyzine.com/Issue001/Section_Articles/article_ThreadingGlobalInterpreter.html