当我们写的程序需要并发时,我们就需要用到 Python 中的一些并发库,例如 asyncio
、thread
、 multiprocessing
等,本文主要介绍 Python 标准库中的多线程库 thread
threading 基本使用
使用多线程的优势在于
- 程序运行更快
- 适用于 IO 密集的场景
Python 标准库提供了两个模块,_thread
和 threading
,threading
对 _thread
进行了封装,虽然 Python 有 GIL
,会在线程切换时消耗很多资源,但是在 IO 密集的场景下,Python 多线程还是很管用的
先看看 threading
的基本使用
import threading |
实例化线程对象,target
参数为指定函数,args
是传递的列表参数,kwargs
是传递的字典参数,通过 start 方法启动一个线程
1, 2, 3], kwargs={'a': 'b'}) t = threading.Thread(target=hello, args=[ |
threading 和 Thread 常用参数和方法
name 参数
导入 logging
库,更加直观的显示线程的信息
import logging |
daemon
线程退出的时候,该线程的所有daemon
的子线程都会退出,而no-daemon
子线程不会退出
而线程退出会等待所有的 no-daemon
子线程退出
join 方法
子线程的 join
方法会阻塞主线程,直到子线程退出或者超时,如果不设置超时时间,就会一直等待子线程退出
import threading |
执行 main
函数能够感觉到是一直阻塞着的,直到子线程退出
main() |
enumerate 方法
列出当前所有的线程
enumerate() threading. |
local
线程共享内存、状态和资源,但是threading
local
对象的属性,只对当前线程可见
ctx = threading.local() |
实例化 Thread 类
之前通过 target
参数的方式不是非常的优雅,其实可以通过继承 Thread
类并重写 run
方法来编写更加优雅的代码
class MyThread(threading.Thread): |
传递参数
通过重写 __init__()
方法传递参数
class MyThread(threading.Thread): |
线程同步
在使用多个线程同时操作一个资源的情况下( 例如读文件) ,我们需要控制同一时刻只有一个线程对资源进行操作,这时候就需要一些同步机制,如 锁、队列、条件、事件等
Lock
我们可以通过 threading.Lock
来解决这个问题
Lock
对象一般有两个操作,获取 acquire
和 释放 release
通过 acquire
方法 将 Lock
对象状态设置为锁定,如果是锁定状态则会阻塞,release
方法则将 Lock
对象解锁
import threading |
一个抓取页面的例子,通过使用锁,实现了线程之间的同步
import requests |
测试
for url in urls] ts = [FetchUrls(url, file, lock, name=url) |
RLock
RLock
是一个可重用锁,可以多次调用 acquire
而不阻塞,但是 release
时也要执行和 acquire
一样的次数
import threading |
Condition
如果多个线程使用 生产者 —> 消费者的模式,可以使用 Condition
,生产者生产数据后,通过 notify/notify_all
通知给消费者消费数据
import threading |
测试
runner() |
Event
Event
是一个简单的机制,线程发出一个信号,其他线程等待
import threading |
测试
runner() |
Queue
之前的几个 提供者 —> 消费者 的例子 一直用一个全局的列表来传递数据,其实不是很科学,不同线程传递数据应该使用 Queue
,因为 Queue
本身也可以阻塞线程,使用 Queue
还可以省去同步
import queue |
测试
runner() |
GIL
提到 Python 多线程就一定要说说 GIL
Global Interpreter Lock
全局解释器锁,由于 GIL
的存在,Python 的线程不能达到真正的并行,在 CPython (C语言实现的 Python)
中 线程使用的是操作系统原生的线程
CPython 中,一个解释器有一条主线程,和若干条用户程序的线程,由于 GIL
的存在,每一个进程要执行时,都要去获取 GIL
,所以并不能有效的利用多核 CPU 实现多线程并行,也就是说,多个线程不能够同时执行
如果要实现真正的并行,就需要使用 multiprocessing
这个多进程模块了
参考资料