python 线程机制
在 Python 中,线程是实现并发编程的一种方式,允许程序同时执行多个任务。以下是 Python 线程机制的详细解说:
1. 线程基础概念
线程 vs 进程
- 进程:程序在操作系统中的一次执行实例,拥有独立的内存空间和系统资源。
- 线程:进程内的一个执行单元,共享进程的内存和资源。
- 区别:线程轻量级,创建和切换开销小;进程隔离性强,稳定性高。
Python 线程的特点
- 全局解释器锁(GIL):CPython 解释器同一时间只能执行一个线程的字节码,导致多线程无法利用多核 CPU 的并行计算能力。
- 适用场景:IO 密集型任务(如网络请求、文件读写),不适合 CPU 密集型任务(如科学计算)。
2. 线程模块:threading
Python 标准库的 threading 模块提供了高级线程 API,支持创建、控制和管理线程。
创建线程的两种方式
方式 1:继承 Thread 类
import threading
class MyThread(threading.Thread):
def __init__(self, name):
super().__init__()
self.name = name
def run(self): # 线程执行的入口函数
print(f"线程 {self.name} 开始")
for i in range(3):
print(f"{self.name}: {i}")
print(f"线程 {self.name} 结束")
# 创建并启动线程
t1 = MyThread("线程A")
t2 = MyThread("线程B")
t1.start() # 调用 start() 方法启动线程,会自动调用 run()
t2.start()
t1.join() # 等待线程结束
t2.join()
方式 2:传入目标函数
import threading
def worker(name):
print(f"线程 {name} 开始")
for i in range(3):
print(f"{name}: {i}")
print(f"线程 {name} 结束")
# 创建并启动线程
t1 = threading.Thread(target=worker, args=("线程C",))
t2 = threading.Thread(target=worker, args=("线程D",))
t1.start()
t2.start()
t1.join()
t2.join()
线程同步机制
锁(Lock)
用于保护共享资源,避免多线程竞争导致的数据不一致。
import threading
counter = 0
lock = threading.Lock()
def increment():
global counter
for _ in range(100000):
lock.acquire() # 获取锁
try:
counter += 1
finally:
lock.release() # 释放锁(确保异常时也能释放)
# 等价写法(使用 with 语句自动管理锁)
def increment_with():
global counter
for _ in range(100000):
with lock: # 自动获取和释放锁
counter += 1
t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"Counter: {counter}") # 正确结果应为 200000
RLock(可重入锁)
允许同一线程多次获取同一把锁,避免死锁。
lock = threading.RLock()
def recursive_function(n):
with lock:
if n == 0:
return
print(f"递归层级: {n}")
recursive_function(n-1)
threading.Thread(target=recursive_function, args=(5,)).start()
信号量(Semaphore)
控制同时访问资源的线程数量。
semaphore = threading.Semaphore(2) # 最多允许2个线程同时访问
def worker(id):
with semaphore:
print(f"线程 {id} 获得信号量")
# 模拟耗时操作
import time
time.sleep(1)
print(f"线程 {id} 释放信号量")
for i in range(5):
threading.Thread(target=worker, args=(i,)).start()
条件变量(Condition)
用于线程间的消息传递和协作。
condition = threading.Condition()
data_ready = False
def producer():
global data_ready
with condition:
print("生产者: 准备数据...")
import time
time.sleep(2)
data_ready = True
condition.notify() # 通知等待的线程
def consumer():
with condition:
condition.wait() # 等待通知
print("消费者: 处理数据")
threading.Thread(target=producer).start()
threading.Thread(target=consumer).start()
守护线程(Daemon Thread)
守护线程是后台线程,当主线程退出时会自动终止。
import threading
import time
def daemon_worker():
while True:
print("守护线程运行中...")
time.sleep(1)
# 创建守护线程
t = threading.Thread(target=daemon_worker)
t.daemon = True # 设置为守护线程
t.start()
# 主线程继续执行,3秒后退出
time.sleep(3)
print("主线程退出,守护线程自动终止")
线程间通信
使用队列(queue.Queue)实现线程安全的消息传递。
from queue import Queue
import threading
def producer(queue):
for i in range(5):
queue.put(f"数据 {i}")
print(f"生产者: 放入数据 {i}")
def consumer(queue):
while True:
data = queue.get()
if data is None: # 终止信号
break
print(f"消费者: 取出 {data}")
queue.task_done() # 通知队列任务已完成
q = Queue()
t1 = threading.Thread(target=producer, args=(q,))
t2 = threading.Thread(target=consumer, args=(q,))
t1.start()
t2.start()
t1.join()
q.put(None) # 发送终止信号
t2.join()
3. 线程池(Python 3.2+)
使用 concurrent.futures.ThreadPoolExecutor 管理线程池,避免手动创建和销毁线程的开销。
from concurrent.futures import ThreadPoolExecutor
def task(n):
print(f"处理任务 {n}")
import time
time.sleep(1)
return n * n
# 创建线程池,最多3个工作线程
with ThreadPoolExecutor(max_workers=3) as executor:
# 提交任务并获取结果
results = list(executor.map(task, range(5)))
print("结果:", results) # 输出: [0, 1, 4, 9, 16]
4. GIL 对 Python 线程的影响
- GIL(全局解释器锁):CPython 解释器的限制,同一时间只能有一个线程执行 Python 字节码。
- 影响:
- IO 密集型任务:线程在等待 IO 时会释放 GIL,其他线程可执行,因此多线程有效。
- CPU 密集型任务:线程持续占用 GIL,无法利用多核,性能甚至低于单线程。
验证 GIL 的影响:
import threading
import time
# CPU 密集型任务
def cpu_task():
x = 0
for i in range(10**7):
x += i
return x
# 单线程
start = time.time()
cpu_task()
cpu_task()
print(f"单线程耗时: {time.time() - start:.2f}s")
# 多线程
start = time.time()
t1 = threading.Thread(target=cpu_task)
t2 = threading.Thread(target=cpu_task)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"多线程耗时: {time.time() - start:.2f}s") # 通常比单线程更慢
5. 替代方案:多进程与异步编程
多进程(multiprocessing)
每个进程有独立的 Python 解释器和 GIL,适合 CPU 密集型任务。
from multiprocessing import Process
def cpu_intensive_task():
# CPU 密集型计算
sum([i*i for i in range(10**7)])
if __name__ == "__main__":
p1 = Process(target=cpu_intensive_task)
p2 = Process(target=cpu_intensive_task)
p1.start()
p2.start()
p1.join()
p2.join()
异步编程(asyncio)
使用协程(coroutine)实现单线程内的并发,适合 IO 密集型任务。
import asyncio
async def fetch_data(url):
print(f"开始请求 {url}")
await asyncio.sleep(1) # 模拟 IO 等待
print(f"完成请求 {url}")
return f"data from {url}"
async def main():
tasks = [fetch_data(f"url{i}") for i in range(3)]
results = await asyncio.gather(*tasks)
print(results)
asyncio.run(main())
6. 线程安全注意事项
- 避免共享状态:尽量减少多线程共享的全局变量。
- 使用线程安全的数据结构:如
queue.Queue,避免直接操作列表、字典等。 - 正确同步:使用锁、信号量等机制保护共享资源。
- 避免死锁:按固定顺序获取锁,使用
with语句确保锁释放。
总结
- 适用场景:Python 线程适合 IO 密集型任务,如网络爬虫、文件处理。
- 同步工具:
Lock、RLock、Semaphore、Condition用于线程协作。 - 替代方案:CPU 密集型任务使用多进程,高并发 IO 使用异步编程。
合理使用线程机制可以提高程序的并发能力,但需注意 GIL 的限制和线程安全问题。
浙公网安备 33010602011771号