threading是Python标准库中用于多线程编程的模块

1. 简单理解线程

当我们运行一个Python脚本时,它会创建一个新的进程:

1
python main.py

这样操作系统就会启动一个Python进程

我们刚启动时就已经有了一个线程,它就是我们的主线程

而一个进程中可以创建多个线程,这就是多线程编程的核心

假设有一个程序存在三个函数:

1
2
3
receive_data()
process_data()
send_data()

这三个函数在一个线程中只能依次执行:

1
2
3
4
5
receive_data()

process_data()

send_data()

但如果我们希望这三个函数:

1
2
3
receive_data() # 一直接收数据
process_data() # 一直处理数据
send_data() # 一直发送数据

那么就需要创建三个线程,并在每个线程中执行一个函数

1
2
3
4
5
6
7
      Python进程

┌───────┼───────┐
↓ ↓ ↓
Thread1 Thread2 Thread3
│ │ │
接收 处理 发送

同进程中的线程会共享同一个内存空间

Python的多线程模块threading提供了一些函数和类,可以帮助我们创建多线程

使用时需要导入:

1
import threading

因为threading是Python标准库,所以不需要安装

2. threading.Thread:创建线程

threading.Thread可以创建线程

常用构造形式:

1
2
3
4
5
6
7
8
9
threading.Thread(
group=None,
target=None, # 线程执行的函数
name=None, # 线程名称
args=(), # 线程执行函数的位置参数
kwargs=None, # 线程执行函数的关键字参数
*,
daemon=None # 是否守护线程
)

例子,创建一个简单的线程:

1
2
3
4
5
6
7
8
9
10
11
12
import threading

def worker():
print("worker 正在运行")

t = threading.Thread(target=worker)

t.start()

t.join()

print("程序结束")

这里注意,创建线程后,并不会直接执行,需要调用start()方法

target不能写worker(),因为worker()表示立即调用函数

给线程函数传参数:
例如函数:

1
2
3
def worker(name, count):
for i in range(count):
print(name, i)

则可以:

1
2
3
4
t = threading.Thread(
target=worker,
args=("worker-1", 5)
)

也可以:

1
2
3
4
5
6
7
t = threading.Thread(
target=worker,
kwargs={
"name": "worker-1",
"count": 5
}
)

daemon=True表示这个线程是守护线程,不阻止整个Python进程退出

也就是说,当:

1
daemon=False

那么主线程结束,Python通常还要等它
如果:

1
daemon=True

那么主线程和其他非守护线程结束后,Python进程可以直接退出

3. threading.start():启动线程

threading有两个容易混淆的方法:

1
2
start()
run()

start()表示创建新线程,并让新线程执行run():

1
2
3
4
5
6
7
8
9
10
11
主线程

t.start()

操作系统创建线程

新线程

t.run()

target(...)

如果直接:

1
t.run()

不会创建新线程,而是直接执行target(...),也就是相当于普通的函数调用

4. threading.join():等线程结束

join()方法等待线程结束,并返回线程的返回值

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import threading
import time


def worker():
print("开始")
time.sleep(3)
print("结束")


t = threading.Thread(target=worker)

t.start()

print("等待线程")

t.join()

print("整个程序结束")

输出:

1
2
3
4
开始
等待线程(中间等待3秒)
结束
整个程序结束

也可以:

1
t.join(timeout=2)

表示主线程最多阻塞2秒

注意它不会杀死线程,只是主线程不会继续阻塞

5. threading.is_alive():检查线程是否存活

is_alive()方法返回线程是否存活

1
threading.is_alive()

返回:

1
2
True
False

表示线程是否仍然运行

6. threading.current_thread():获取当前线程

current_thread()方法返回当前正在执行代码的线程

例子:

1
2
3
import threading

print(threading.current_thread())

输出:

1
<_MainThread(MainThread, started ...)>

7. threading.main_thread():获取主线程

main_thread()方法返回主线程对象

判断当前线程是否为主线程:

1
threading.main_thread() == threading.current_thread()

如果当前在主线程:

1
True

8. threading.enumerate():获取所有线程

enumerate()方法返回一个包含所有线程的列表

例子:

1
2
3
4
import threading

for t in threading.enumerate():
print(t)

输出:

1
2
3
4
<_MainThread(MainThread, started ...)>
<_Thread(Thread-1, started ...)>
<_Thread(Thread-2, started ...)>
<_Thread(Thread-3, started ...)>

9. threading.active_count():获取活跃线程数量

active_count()方法返回活跃线程数量

例子:

1
2
3
import threading

print(threading.active_count())

输出:

1
3

10. 竞态条件

多个线程同时操作共享数据时,程序最终结果取决于线程执行的先后顺序,而线程的执行顺序又是不确定的

当两个线程共同操作一个变量:

1
count = 0

线程1:

1
count += 1

线程2:

1
count += 1

有人可能认为:

1
2
3
4
5
6
初始 count = 0

线程 A +1
线程 B +1

最终 count = 2

但是 count+=1 从底层逻辑上看并不是一个不可分割的操作,因此可能会出现下面这种情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
初始:

count = 0

线程 A 线程 B
│ 读取 count → 0 │
│ 读取 count → 0
│ 计算 0 + 1 → 1 │
│ 计算 0 + 1 → 1
│ 写入 count = 1 │
│ 写入 count = 1
│ │
▼ ▼

最终:

count = 1

程序员很难预测线程执行顺序,因此这种Bug往往会导致不可预料的结果,并且非常难排查

11. threading.Lock:互斥锁

threading.Lock是threading模块中的一个类,用于实现互斥锁

互斥锁可以防止多个线程同时访问共享资源,从而避免并发问题

创建互斥锁:

1
2
3
import threading

lock = threading.Lock()

核心方法:

1
2
acquire()
release()

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import threading

lock = threading.Lock()

def worker(name):
lock.acquire()
print(name, "开始")
time.sleep(3)
print(name, "结束")
lock.release()

t1 = threading.Thread(target=worker, args=("线程1",))
t2 = threading.Thread(target=worker, args=("线程2",))

t1.start()
t2.start()

t1.join()
t2.join()

输出:

1
2
3
4
线程1 开始
线程2 开始
线程1 结束
线程2 结束

这样:

1
2
3
4
5
6
7
8
9
10
11
Thread 1

acquire()

获得锁

操作

release()

释放锁

如果这个时候Thread 2:

1
lock.acquire()

则会被阻塞,直到Thread 1释放锁

保证同一时刻只有一个线程能获得锁

Python更推荐使用上下文管理器操作锁:

1
2
with lock:
count += 1

等同于:

1
2
3
4
5
6
lock.acquire()

try:
count += 1
finally:
lock.release()

这里的count += 1被称为临界区(Critical Section),也就是访问共享资源,需要保证线程安全的代码区域

12. threading.RLock:可读写锁

创建:

1
lock = threading.RLock()

普通的Lock同一个线程第二次获得锁会出现阻塞,把自己卡住:

1
2
lock.acquire()
lock.acquire()

但是RLock可以多次获得锁

RLock常用于嵌套函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import threading

lock = threading.RLock()

def func1():
with lock:
print("func1")
func2()

def func2():
with lock:
print("func2")

func1()

如果是普通的锁,则这里会出现死锁

13. threading.Event:事件

事件是一个布尔值,只有在事件被设置为True时,才会触发线程

主要方法:

1
2
3
4
event.set() # Event状态设置为True
event.clear() # Event状态设置为False
event.is_set() # 返回Event状态
event.wait() # 如果Event是False,则等待;当Event状态为True,则继续进行

刚创建Event时:

1
event = threading.Event()

内部:

1
False

执行:

1
event.set()

内部:

1
True

执行:

1
event.clear()

内部:

1
False

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import threading
import time

event = threading.Event()

def worker():
print("worker:等待信号")
event.wait()
print("worker:收到信号")

t = threading.Thread(target=worker)
t.start()

time.sleep(2)

print("main:发送信号")
event.set()

输出:

1
2
3
worker:等待信号(等待2秒)
main:发送信号
worker:收到信号

14. threading.Condition:条件变量

条件变量是一个布尔值,只有在条件变量为True时,才会触发线程

作用场景是:一个线程必须等到某个“条件成立”才能继续执行

Condition是一个类,主要方法:

1
2
3
condition.wait() # 等待条件
condition.notify() # 通知线程
condition.notify_all() # 通知所有线程

Condition.wait():让当前线程进入等待状态,直到其他线程调用notify()或notify_all()
Condition.notify():唤醒一个正在wait的线程,但唤醒的线程不依赖具体顺序,也无法指定
Condition.notify(n):唤醒n个正在wait的线程
Condition.notify_all():唤醒所有正在wait的线程

Condition内部关联了一个锁,所以也需要上下文管理器with来获取锁

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import threading
import time

condition = threading.Condition()

def worker():
with condition:
print("worker:开始等待")
condition.wait()
print("worker:被唤醒")

def main():
t = threading.Thread(target=worker)
t.start()

time.sleep(2)

with condition:
print("main:通知 worker")
condition.notify()

t.join()

main()

输出:

1
2
3
worker:开始等待(等待2秒)
main:通知 worker
worker:被唤醒