进程和线程 (Process and Thread)
并发编程是现代软件开发的重要技能。本章将学习Python中的多进程和多线程编程,理解它们的区别、适用场景以及如何避免并发问题。
进程 vs 线程
┌─────────────────────────────────────────────────────────────────┐
│ 进程 vs 线程 对比 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【进程 (Process)】 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 进程1 进程2 进程3 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 代码 │ │ 代码 │ │ 代码 │ │ │
│ │ │ 数据 │ │ 数据 │ │ 数据 │ │ │
│ │ │ 资源 │ │ 资源 │ │ 资源 │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │ │ │ │
│ │ └────────────────┴────────────────┘ │ │
│ │ 操作系统 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ 特点:独立内存空间,进程间隔离,启动开销大 │
│ │
│ 【线程 (Thread)】 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 进程 │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ 线程1 线程2 线程3 主线程 │ │ │
│ │ │ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ │ │
│ │ │ │堆栈│ │堆栈│ │堆栈│ │堆栈│ │ │ │
│ │ │ └────┘ └────┘ └────┘ └────┘ │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ │ 共用代码、数据、资源、文件描述符 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ 特点:共享进程资源,启动开销小,需要同步 │
│ │
└─────────────────────────────────────────────────────────────────┘
| 特性 | 进程 | 线程 |
|---|---|---|
| 内存空间 | 独立 | 共享进程内存 |
| 创建开销 | 大 | 小 |
| 通信方式 | Pipe, Queue, 共享内存 | 直接共享变量(需同步) |
| 稳定性 | 高(隔离) | 低(共享资源) |
| 适用场景 | CPU密集型、并行计算 | IO密集型、异步操作 |
Python GIL 限制
由于Python的GIL(全局解释器锁),多线程不能真正并行执行CPU密集型任务。
对于CPU密集型任务,应使用多进程;对于IO密集型任务,多线程仍然有效。
由于Python的GIL(全局解释器锁),多线程不能真正并行执行CPU密集型任务。
对于CPU密集型任务,应使用多进程;对于IO密集型任务,多线程仍然有效。
threading 多线程
1. 创建线程
Python
import threading
import time
# 方式1: 创建 Thread 对象
def task(name, seconds):
print(f"线程 {name} 开始")
time.sleep(seconds)
print(f"线程 {name} 结束")
# 创建线程
t1 = threading.Thread(target=task, args=("A", 1))
t2 = threading.Thread(target=task, args=("B", 0.5))
# 启动线程
t1.start()
t2.start()
# 等待线程结束
t1.join()
t2.join()
print("所有线程完成")
2. 继承 Thread 类
Python
class MyThread(threading.Thread):
def __init__(self, name, delay):
super().__init__()
self.name = name
self.delay = delay
def run(self):
print(f"线程 {self.name} 开始执行")
time.sleep(self.delay)
print(f"线程 {self.name} 执行完成")
threads = [
MyThread("Thread-1", 1),
MyThread("Thread-2", 0.5),
]
for t in threads:
t.start()
for t in threads:
t.join()
print("主线程继续执行")
3. 线程同步 - 锁
Python
import threading
# 共享资源
counter = 0
counter_lock = threading.Lock()
def increment():
global counter
for _ in range(100000):
with counter_lock: # 获取锁
counter += 1
# 创建多个线程
threads = [threading.Thread(target=increment) for _ in range(5)]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"计数器最终值: {counter}")
ThreadLocal
ThreadLocal 为每个线程提供独立的变量副本,避免线程间的变量冲突。
┌─────────────────────────────────────────────────────────────────┐
│ ThreadLocal 工作原理 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ local_data = threading.local() │
│ │
│ 线程1 线程2 线程3 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐│
│ │ local_data │ │ local_data │ │ local_data ││
│ │ .x = 1 │ │ .x = 2 │ │ .x = 3 ││
│ │ .y = "A" │ │ .y = "B" │ │ .y = "C" ││
│ └────────────┘ └────────────┘ └────────────┘│
│ ▲ ▲ ▲ │
│ │ │ │ │
│ └───────────────────────┴───────────────────────┘ │
│ ThreadLocal │
│ (每个线程看到的是自己的副本) │
│ │
└─────────────────────────────────────────────────────────────────┘
Python
import threading
# ThreadLocal 对象
local_data = threading.local()
def process():
# 每个线程独立设置自己的数据
local_data.value = threading.current_thread().name
print(f"线程 {threading.current_thread().name}: local_data.value = {local_data.value}")
# 创建多个线程
threads = [threading.Thread(target=process) for _ in range(3)]
for t in threads:
t.start()
for t in threads:
t.join()
multiprocessing 多进程
1. 基本用法
Python
from multiprocessing import Process, Pool
import os
def worker(name):
print(f"进程 {name} (PID: {os.getpid()}) 开始")
print(f"进程 {name} 结束")
# 创建多个进程
if __name__ == "__main__":
processes = []
for i in range(3):
p = Process(target=worker, args=(i,))
processes.append(p)
p.start()
for p in processes:
p.join()
print(f"主进程 PID: {os.getpid()}")
2. 进程池 Pool
Python
from multiprocessing import Pool
import time
def square(n):
return n * n
if __name__ == "__main__":
# 创建进程池(4个工作进程)
with Pool(processes=4) as pool:
# map - 并行执行
results = pool.map(square, [1, 2, 3, 4, 5])
print(f"map 结果: {results}")
# apply_async - 异步执行
result = pool.apply_async(square, (10,))
print(f"apply_async 结果: {result.get()}")
# 批量异步
results = pool.map_async(square, [6, 7, 8])
print(f"map_async 结果: {results.get()}")
3. 进程间通信
Python
from multiprocessing import Process, Queue
def producer(queue):
for i in range(5):
queue.put(i)
queue.put(None) # 发送结束信号
def consumer(queue):
while True:
item = queue.get()
if item is None:
break
print(f"消费: {item}")
if __name__ == "__main__":
queue = Queue()
p1 = Process(target=producer, args=(queue,))
p2 = Process(target=consumer, args=(queue,))
p1.start()
p2.start()
p1.join()
p2.join()
知识测验
问题1:Python多线程不能真正并行执行的原因是?
问题2:ThreadLocal的作用是什么?
课后练习
练习1:多线程下载器
实现一个简单的多线程任务模拟:
- 创建多个线程,每个线程下载不同的"文件"
- 使用ThreadLocal存储每个线程的下载进度
- 使用锁保护共享的完成计数
- 等待所有线程完成后输出总进度
练习2:进程池并行计算
使用进程池进行并行计算:
- 创建一个计算密集型函数(如求素数)
- 使用进程池并行计算多个范围内的素数
- 对比串行和并行的执行时间
🐍 Python 在线代码编辑器
📝 运行结果