文章目录
- import threading, time # 方式一:把函数塞给 Thread def worker(n): print(f’Worker {n} start’) time.sleep(1) print(f’Worker {n} done’) for i in range(3): t = threading.Thread(target=worker, args=(i,)) t.start() # 方式二:继承 Thread 并重写 run class MyThread(threading.Thread): def __init__(self, n): super().__init__() self.n = n def run(self): print(f’MyThread {self.n} start’) time.sleep(1) print(f’MyThread {self.n} done’) MyThread(10).start()
- start() 只是告诉操作系统“可以调度了”,不保证立即执行。 threads = [threading.Thread(target=worker, args=(i,)) for i in range(3)] [t.start() for t in threads] [t.join() for t in threads] # 等全部结束 print(‘all done’)
- 竞争最激烈的原语,解决“读写交叉”问题。 counter = 0 lock = threading.Lock() def add(): global counter for _ in range(100000): with lock: # 等价于 lock.acquire(); try: … finally: lock.release() counter += 1 threads = [threading.Thread(target=add) for _ in range(2)] [t.start() for t in threads] [t.join() for t in threads] print(counter) # 200000 没有 lock 时,大概率得到 <200000 的错误结果。
- 同一个线程可以多次 acquire,避免死锁。 rlock = threading.RLock() def foo(): with rlock: bar() def bar(): with rlock: # 同一线程,再次获取成功 pass
- 经典“生产者-消费者”模型: import random, time q, MAX = [], 5 cond = threading.Condition() def producer(): while True: with cond: while len(q) == MAX: cond.wait() # 等待队列有空位 item = random.randint(1, 100) q.append(item) print(‘+’, item, q) cond.notify() # 通知消费者 time.sleep(0.5) def consumer(): while True: with cond: while not q: cond.wait() item = q.pop(0) print(‘-‘, item, q) cond.notify() time.sleep(0.6) threading.Thread(target=producer, daemon=True).start() threading.Thread(target=consumer, daemon=True).start() time.sleep(5)
- 控制并发数量,例如“最多 3 个线程同时下载”。 sem = threading.Semaphore(3) def downloader(url): with sem: print(‘downloading’, url) time.sleep(2)
- 线程间“发令枪”机制: event = threading.Event() def waiter(): print(‘wait…’) event.wait() # 阻塞 print(‘go!’) threading.Thread(target=waiter).start() time.sleep(3) event.set() # 发令
- N 个线程同时到达某点后再一起继续,适合分阶段任务。 barrier = threading.Barrier(3) def phase(name): print(name, ‘ready’) barrier.wait() print(name, ‘go’) for i in range(3): threading.Thread(target=phase, args=(i,)).start()
- 保持加锁顺序一致。 使用 try-lock + 超时。 借助第三方库 deadlock-debug 或 faulthandler。
- CPU 密集:换多进程、Cython、NumPy、multiprocessing。 I/O 密集:放心用线程,瓶颈在网络延迟而非 GIL。
- 当只剩守护线程时,程序直接退出。 常用于后台心跳、日志写入,但不要做重要数据持久化。
- logging.basicConfig( format=’%(asctime)s [%(threadName)s] %(message)s’, level=logging.INFO)
- GUI 程序:UI 线程勿阻塞,耗时操作放后台线程。 Web 服务:WSGI 服务器(uWSGI、gunicorn)已帮你管理进程/线程,业务代码慎用线程。
目录
- 1. 为什么需要线程
- 2. 从 0 开始写线程
- 2.1 创建线程的两种姿势
- 2.2 join:别让主线程提前跑路
- 3. 线程同步:共享变量的“安全带”
- 3.1 Lock(互斥锁)
- 3.2 RLock(可重入锁)
- 3.3 Condition(条件变量)
- 3.4 Semaphore(信号量)
- 3.5 Event(事件)
- 3.6 Barrier(栅栏)
- 4. 线程局部变量:ThreadLocal
- 5. 定时器 Timer:延时任务
- 6. 线程池:高并发下的“资源管家”
- 7. 调试与最佳实践
- 7.1 死锁排查
- 7.2 GIL 与性能
- 7.3 守护线程 daemon
- 7.4 日志线程名
- 7.5 不要滥用
- 8. 总结
“并发不等于并行,但并发能让生活更美好。”——《Python 并发编程实战》
CPU 单核性能已逼近物理极限,要想让程序在相同时间内做更多事,必须“同时”做多件事。
- 多进程 Process:利用多核并行,资源隔离但开销大。
- 协程 Coroutine:单线程内切换,极致 I/O 友好,但无法利用多核。
- 线程 Thread:介于两者之间,共享内存、切换快,是 I/O 密集型任务的首选。
在 Python 里,GIL(Global Interpreter Lock)限制了同一进程内只能有一条字节码在执行,进而“弱化”了线程在多核 CPU 上的并行能力。然而:
- 线程在等待 I/O 时会主动释放 GIL,因此下载、爬虫、聊天服务器等网络/磁盘 I/O 场景依旧收益巨大。
- 对 CPU 密集型任务,可用 multiprocessing 或 C 扩展绕开 GIL。
一句话:
当你想让程序“边读边写”“边收边发”“边阻塞边响应”,就用 threading。
import threading, time
# 方式一:把函数塞给 Thread
def worker(n):
print(f'Worker {n} start')
time.sleep(1)
print(f'Worker {n} done')
for i in range(3):
t = threading.Thread(target=worker, args=(i,))
t.start()
# 方式二:继承 Thread 并重写 run
class MyThread(threading.Thread):
def __init__(self, n):
super().__init__()
self.n = n
def run(self):
print(f'MyThread {self.n} start')
time.sleep(1)
print(f'MyThread {self.n} done')
MyThread(10).start()
start() 只是告诉操作系统“可以调度了”,不保证立即执行。
threads = [threading.Thread(target=worker, args=(i,)) for i in range(3)]
[t.start() for t in threads]
[t.join() for t in threads] # 等全部结束
print('all done')
竞争最激烈的原语,解决“读写交叉”问题。
counter = 0
lock = threading.Lock()
def add():
global counter
for _ in range(100000):
with lock: # 等价于 lock.acquire(); try: ... finally: lock.release()
counter += 1
threads = [threading.Thread(target=add) for _ in range(2)]
[t.start() for t in threads]
[t.join() for t in threads]
print(counter) # 200000
没有 lock 时,大概率得到 <200000 的错误结果。
同一个线程可以多次 acquire,避免死锁。
rlock = threading.RLock()
def foo():
with rlock:
bar()
def bar():
with rlock: # 同一线程,再次获取成功
pass
经典“生产者-消费者”模型:
import random, time
q, MAX = [], 5
cond = threading.Condition()
def producer():
while True:
with cond:
while len(q) == MAX:
cond.wait() # 等待队列有空位
item = random.randint(1, 100)
q.append(item)
print('+', item, q)
cond.notify() # 通知消费者
time.sleep(0.5)
def consumer():
while True:
with cond:
while not q:
cond.wait()
item = q.pop(0)
print('-', item, q)
cond.notify()
time.sleep(0.6)
threading.Thread(target=producer, daemon=True).start()
threading.Thread(target=consumer, daemon=True).start()
time.sleep(5)
控制并发数量,例如“最多 3 个线程同时下载”。
sem = threading.Semaphore(3)
def downloader(url):
with sem:
print('downloading', url)
time.sleep(2)
线程间“发令枪”机制:
event = threading.Event()
def waiter():
print('wait...')
event.wait() # 阻塞
print('go!')
threading.Thread(target=waiter).start()
time.sleep(3)
event.set() # 发令
N 个线程同时到达某点后再一起继续,适合分阶段任务。
barrier = threading.Barrier(3)
def phase(name):
print(name, 'ready')
barrier.wait()
print(name, 'go')
for i in range(3):
threading.Thread(target=phase, args=(i,)).start()
共享虽好,可有时我们想让每个线程拥有“私有副本”。
local = threading.local()
def show():
print(f'{threading.current_thread().name} -> {local.x}')
def task(n):
local.x = n
show()
for i in range(3):
threading.Thread(target=task, args=(i,)).start()
def hello():
print('hello, timer')
threading.Timer(3.0, hello).start()
常用于“超时取消”“心跳包”等场景。
频繁创建/销毁线程代价高昂,Python 3.2+ 内置 concurrent.futures.ThreadPoolExecutor 提供池化能力。
from concurrent.futures import ThreadPoolExecutor
import requests, time
URLS = ['https://baidu.com'] * 20
def fetch(url):
return requests.get(url).status_code
with ThreadPoolExecutor(max_workers=10) as pool:
for code in pool.map(fetch, URLS):
print(code)
- max_workers 默认为
min(32, os.cpu_count() + 4),I/O 密集场景可调高。 submit+as_completed组合可实现“谁先完成谁处理”。
- 保持加锁顺序一致。
- 使用
try-lock + 超时。
- 借助第三方库 deadlock-debug 或 faulthandler。
try-lock + 超时。
- CPU 密集:换多进程、Cython、NumPy、multiprocessing。
- I/O 密集:放心用线程,瓶颈在网络延迟而非 GIL。
- 当只剩守护线程时,程序直接退出。
- 常用于后台心跳、日志写入,但不要做重要数据持久化。
logging.basicConfig(
format='%(asctime)s [%(threadName)s] %(message)s',
level=logging.INFO)
- GUI 程序:UI 线程勿阻塞,耗时操作放后台线程。
- Web 服务:WSGI 服务器(uWSGI、gunicorn)已帮你管理进程/线程,业务代码慎用线程。
| 维度 | 线程 | 进程 | 协程 |
|---|---|---|---|
| 内存开销 | 低 | 高 | 极低 |
| 数据共享 | 易 | 难(需 IPC) | 易 |
| 切换成本 | 中 | 高 | 极低 |
| 适合场景 | I/O 密集 | CPU 密集 | 超高并发 I/O |
| Python 限制 | GIL | 无 | 无 |
使用 threading 的黄金法则:
- 明确任务是 I/O 密集。
- 共享变量就用锁,或者别共享。
- 用 ThreadPoolExecutor 减少手工创建。
- 守护线程只干辅助活。
- 调试时给线程起名字、打日志。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持风君子博客。
您可能感兴趣的文章:
- Python 基于线程的并行 threading模块的用法
- Python多线程threading模块实例详解
- python在pyqt5+logging+threading模块实时显示日志代码示例
- Python 中 threading.Thread.join() 的使用方法示例详解
- Python多线程编程threading模块使用最佳实践及常见问题解析