Python 中的并发编程有多种方式,包括多线程、多进程、协程,以及与之相关的锁、同步和异步机制。下面将分别进行详解,并给出应用场景举例。
1. 多线程 (Threading)
多线程允许在同一个进程中运行多个线程,共享内存空间。由于全局解释器锁(GIL)的存在,Python 的多线程并不适合计算密集型任务,但适合 I/O 密集型任务。
详解:
-
线程是操作系统能够进行运算调度的最小单位,被包含在进程之中。
-
在 Python 中,
threading模块提供了线程相关的操作。 -
由于 GIL,同一时刻只有一个线程可以执行 Python 字节码,因此多线程在 CPU 密集型任务中并不能并行执行,但在 I/O 密集型任务中,当线程等待 I/O 时,其他线程可以运行。
应用场景:
-
网络请求(如下载多个文件)。
-
处理多个客户端的服务器。
-
任何有大量等待(I/O)的任务。
举例:
python
import threading
import time
def download_file(url):
print(f"开始下载 {url}")
time.sleep(2) # 模拟网络延迟
print(f"下载完成 {url}")
urls = ["http://example.com/file1", "http://example.com/file2", "http://example.com/file3"]
threads = []
for url in urls:
thread = threading.Thread(target=download_file, args=(url,))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
2. 多进程 (Multiprocessing)
多进程可以绕过 GIL,实现真正的并行计算,因为每个进程有自己独立的 GIL。适合计算密集型任务。
详解:
-
进程是资源分配的最小单位,每个进程有独立的内存空间。
-
使用
multiprocessing模块可以创建进程。 -
进程间通信(IPC)可以使用队列、管道等。
应用场景:
-
计算密集型任务,如图像处理、数值计算等。
-
需要充分利用多核 CPU 的任务。
举例:
python
import multiprocessing
import time
def calculate_square(numbers):
for n in numbers:
time.sleep(0.01) # 模拟计算耗时
print(f"平方 {n}: {n*n}")
if __name__ == "__main__":
numbers = list(range(10))
# 创建两个进程
p1 = multiprocessing.Process(target=calculate_square, args=(numbers[:5],))
p2 = multiprocessing.Process(target=calculate_square, args=(numbers[5:],))
p1.start()
p2.start()
p1.join()
p2.join()
3. 协程 (Coroutine)
协程是一种用户态的轻量级线程,由用户控制调度,开销远小于线程。在 Python 中,协程通常与 asyncio 库一起使用,用于异步编程。
详解:
-
协程可以在同一个线程中实现并发,通过事件循环来调度。
-
使用
async和await关键字定义协程。 -
适合 I/O 密集型任务,且可以处理大量并发连接。
应用场景:
-
高并发的网络应用,如 Web 服务器。
-
爬虫并发下载。
-
实时数据处理。
举例:
python
import asyncio
import aiohttp
async def fetch_url(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def main():
urls = ["http://example.com", "http://example.org", "http://example.net"]
tasks = [fetch_url(url) for url in urls]
results = await asyncio.gather(*tasks)
for url, content in zip(urls, results):
print(f"{url}: 获取了 {len(content)} 字节")
asyncio.run(main())
4. 锁 (Lock)
在多线程或多进程中,当共享资源时,为了防止竞争条件,需要使用锁来确保同一时间只有一个线程或进程访问共享资源。
详解:
-
锁有两种状态:锁定和未锁定。
-
使用锁可以保证数据一致性,但过度使用会导致性能下降。
应用场景:
-
多个线程或进程需要修改同一个共享变量。
-
对文件或数据库的写操作。
举例(多线程中的锁):
python
import threading
counter = 0
lock = threading.Lock()
def increment():
global counter
for _ in range(100000):
lock.acquire()
counter += 1
lock.release()
threads = []
for _ in range(2):
thread = threading.Thread(target=increment)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
print(f"最终计数器值: {counter}")
5. 同步 (Synchronization)
同步机制用于协调多个线程或进程的执行顺序,确保它们按照一定的顺序或条件执行。除了锁,还有信号量、事件、条件变量等。
应用场景:
-
生产者-消费者问题。
-
限制同时访问资源的线程数。
举例(使用信号量):
python
import threading
import time
semaphore = threading.Semaphore(3) # 最多允许3个线程同时访问
def access_resource(thread_id):
semaphore.acquire()
print(f"线程 {thread_id} 正在访问资源")
time.sleep(2)
print(f"线程 {thread_id} 释放资源")
semaphore.release()
threads = []
for i in range(10):
thread = threading.Thread(target=access_resource, args=(i,))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
6. 异步 (Asynchronous)
异步编程允许程序在等待 I/O 操作时继续执行其他任务,从而提高效率。在 Python 中,异步编程通常使用 asyncio 库。
详解:
-
异步编程基于事件循环和协程。
-
使用
async定义异步函数,await来等待异步操作完成。 -
适合 I/O 密集型且需要高并发的场景。
应用场景:
-
高性能网络服务器(如 aiohttp)。
-
异步数据库驱动。
-
实时消息推送。
举例(异步文件读写,使用 aiofiles):
python
import asyncio
import aiofiles
async def write_file(filename, content):
async with aiofiles.open(filename, 'w') as f:
await f.write(content)
print(f"写入完成 {filename}")
async def main():
tasks = []
for i in range(5):
tasks.append(write_file(f"file_{i}.txt", f"内容 {i}"))
await asyncio.gather(*tasks)
asyncio.run(main())
总结
-
多线程:适合 I/O 密集型任务,由于 GIL 的存在,不适合计算密集型任务。
-
多进程:适合计算密集型任务,可以充分利用多核 CPU,但进程间通信开销较大。
-
协程:适合高并发的 I/O 密集型任务,轻量级,由用户控制调度,通常与异步编程结合。
-
锁:用于解决多线程或多进程中的共享资源竞争问题。
-
同步:协调多个线程或进程的执行顺序。
-
异步:基于事件循环,提高 I/O 密集型任务的并发性能。
根据不同的任务类型和需求,选择合适的技术。
Python 并发编程详解
一、多线程 (Multithreading)
特点:
-
共享内存:线程间共享进程的内存空间
-
受GIL限制:Python全局解释器锁(GIL)限制同一时刻只有一个线程执行Python字节码
-
适合I/O密集型任务:如网络请求、文件读写
-
轻量级:创建和切换开销小
应用场景:
python
import threading
import time
import requests
# 1. 多线程下载文件
def download_file(url, filename):
response = requests.get(url)
with open(filename, 'wb') as f:
f.write(response.content)
print(f"{filename} 下载完成")
# 场景:批量下载文件
urls = [('http://example.com/file1', 'file1.txt'),
('http://example.com/file2', 'file2.txt')]
threads = []
for url, filename in urls:
t = threading.Thread(target=download_file, args=(url, filename))
t.start()
threads.append(t)
for t in threads:
t.join()
# 2. Web服务器处理并发请求
class WebServer:
def handle_request(self, client_id):
time.sleep(0.5) # 模拟处理时间
print(f"处理客户端 {client_id} 的请求")
def start(self):
for i in range(10):
t = threading.Thread(target=self.handle_request, args=(i,))
t.start()
优势:
-
快速响应用户界面
-
适合I/O等待时间长的任务
-
内存占用小
二、多进程 (Multiprocessing)
特点:
-
独立内存空间:每个进程有独立的内存,需进程间通信(IPC)
-
不受GIL限制:可真正并行执行CPU密集型任务
-
开销较大:创建和切换开销比线程大
-
适合CPU密集型任务
应用场景:
python
import multiprocessing
import math
from multiprocessing import Pool
# 1. CPU密集型计算
def cpu_intensive_task(n):
"""计算质数个数"""
count = 0
for i in range(2, n):
is_prime = True
for j in range(2, int(math.sqrt(i)) + 1):
if i % j == 0:
is_prime = False
break
if is_prime:
count += 1
return count
# 场景:大数据计算
def process_demo():
numbers = [100000, 150000, 200000, 250000]
# 单进程
start = time.time()
results = [cpu_intensive_task(n) for n in numbers]
print(f"单进程耗时: {time.time() - start}")
# 多进程
start = time.time()
with Pool(processes=4) as pool:
results = pool.map(cpu_intensive_task, numbers)
print(f"多进程耗时: {time.time() - start}")
# 2. 生产者-消费者模式
def producer(queue, items):
for item in items:
queue.put(item)
time.sleep(0.1)
def consumer(queue, name):
while True:
item = queue.get()
if item is None:
break
print(f"{name} 处理: {item}")
def producer_consumer_demo():
queue = multiprocessing.Queue()
# 创建生产者
items = [f"任务{i}" for i in range(20)]
p1 = multiprocessing.Process(target=producer, args=(queue, items))
# 创建消费者
c1 = multiprocessing.Process(target=consumer, args=(queue, "消费者1"))
c2 = multiprocessing.Process(target=consumer, args=(queue, "消费者2"))
p1.start()
c1.start()
c2.start()
p1.join()
queue.put(None) # 结束信号
queue.put(None)
c1.join()
c2.join()
优势:
-
充分利用多核CPU
-
内存隔离更安全
-
适合计算密集型任务
三、协程 (Coroutine)
特点:
-
用户态调度:由程序控制切换,开销极小
-
异步非阻塞:适合高并发I/O
-
单线程内并发:在同一个线程内切换
-
Python 3.5+支持async/await语法
应用场景:
python
import asyncio
import aiohttp
# 1. 高并发网络请求
async def fetch_url(session, url):
async with session.get(url) as response:
return await response.text()
async def async_demo():
urls = [
'http://httpbin.org/delay/1',
'http://httpbin.org/delay/2',
'http://httpbin.org/delay/1'
]
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
for url, content in zip(urls, results):
print(f"{url}: {len(content)} bytes")
# 2. WebSocket服务器
async def handle_client(reader, writer):
addr = writer.get_extra_info('peername')
print(f"客户端 {addr} 连接")
while True:
data = await reader.read(100)
if not data:
break
message = data.decode()
print(f"收到: {message}")
writer.write(f"已收到: {message}".encode())
await writer.drain()
writer.close()
async def websocket_server():
server = await asyncio.start_server(
handle_client, '127.0.0.1', 8888)
async with server:
await server.serve_forever()
# 3. 生产者-消费者协程版
async def async_producer(queue, n):
for i in range(n):
await queue.put(f"产品{i}")
await asyncio.sleep(0.1)
await queue.put(None)
async def async_consumer(queue, name):
while True:
item = await queue.get()
if item is None:
break
print(f"{name} 消费: {item}")
await asyncio.sleep(0.2)
async def async_producer_consumer():
queue = asyncio.Queue(maxsize=10)
producer_task = asyncio.create_task(async_producer(queue, 20))
consumer_tasks = [
asyncio.create_task(async_consumer(queue, "消费者1")),
asyncio.create_task(async_consumer(queue, "消费者2"))
]
await producer_task
await queue.put(None) # 通知消费者结束
await asyncio.gather(*consumer_tasks)
优势:
-
极高的并发性能(可支持上万并发连接)
-
极低的资源消耗
-
代码结构清晰(类似同步代码)
四、锁与同步机制
1. 线程锁
python
import threading
# 互斥锁
class BankAccount:
def __init__(self, balance):
self.balance = balance
self.lock = threading.Lock()
def transfer(self, amount):
with self.lock: # 自动获取和释放锁
old_balance = self.balance
self.balance += amount
print(f"{old_balance} -> {self.balance}")
# 递归锁(可重入锁)
class RecursiveExample:
def __init__(self):
self.rlock = threading.RLock()
self.value = 0
def method1(self):
with self.rlock:
self.value += 1
self.method2() # 可以再次获取同一个锁
def method2(self):
with self.rlock:
self.value -= 1
# 信号量
class ConnectionPool:
def __init__(self, max_connections):
self.semaphore = threading.Semaphore(max_connections)
def get_connection(self):
self.semaphore.acquire()
# 获取连接
return Connection()
def release_connection(self, conn):
# 释放连接
self.semaphore.release()
# 事件
class Worker:
def __init__(self, event):
self.event = event
def run(self):
print("等待信号...")
self.event.wait() # 阻塞直到事件被设置
print("收到信号,开始工作")
def event_demo():
event = threading.Event()
workers = [Worker(event) for _ in range(3)]
threads = []
for worker in workers:
t = threading.Thread(target=worker.run)
t.start()
threads.append(t)
time.sleep(2)
print("发送开始信号")
event.set() # 唤醒所有等待的线程
2. 进程锁
python
from multiprocessing import Lock, Value, Process
def increment_counter(lock, counter):
for _ in range(1000):
with lock:
counter.value += 1
def process_lock_demo():
counter = Value('i', 0)
lock = Lock()
processes = []
for _ in range(4):
p = Process(target=increment_counter, args=(lock, counter))
p.start()
processes.append(p)
for p in processes:
p.join()
print(f"最终计数: {counter.value}")
五、异步与同步对比
同步阻塞模式:
python
import socket
def sync_client():
# 同步阻塞式客户端
sock = socket.socket()
sock.connect(('example.com', 80)) # 阻塞直到连接建立
sock.send(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')
data = sock.recv(1024) # 阻塞直到数据到达
print(data.decode())
sock.close()
异步非阻塞模式:
python
import asyncio
async def async_client():
# 异步非阻塞式客户端
reader, writer = await asyncio.open_connection('example.com', 80)
writer.write(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')
await writer.drain()
data = await reader.read(1024)
print(data.decode())
writer.close()
六、选择指南
如何选择并发模型:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| CPU密集型计算 | 多进程 | 绕过GIL,真正并行 |
| I/O密集型,高并发 | 协程 | 轻量级,高并发 |
| 简单并行任务 | 线程池 | 易于使用 |
| GUI应用 | 多线程 | 保持UI响应 |
| 数据处理流水线 | 多进程+队列 | 充分利用多核 |
| Web服务器 | 异步+协程 | 高并发连接 |
性能对比示例:
python
import concurrent.futures
import asyncio
import aiohttp
import requests
def thread_download(urls):
"""线程池下载"""
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
futures = [executor.submit(requests.get, url) for url in urls]
results = [f.result() for f in futures]
return results
async def async_download(urls):
"""异步下载"""
async with aiohttp.ClientSession() as session:
tasks = [session.get(url) for url in urls]
responses = await asyncio.gather(*tasks)
results = [await r.text() for r in responses]
return results
# 根据场景选择:
# - 少量大文件:多线程
# - 大量小文件:协程
# - 复杂计算:多进程
七、最佳实践
-
避免共享状态:尽量使用消息传递而非共享内存
-
使用线程/进程池:避免频繁创建销毁
-
正确处理异常:防止静默失败
-
设置超时:避免死锁
-
资源清理:确保释放锁、连接等资源
python
# 使用上下文管理器确保资源释放
def safe_transfer(account1, account2, amount):
# 按固定顺序获取锁,避免死锁
lock1, lock2 = sorted([account1.lock, account2.lock], key=id)
with lock1:
with lock2:
account1.withdraw(amount)
account2.deposit(amount)
# 使用 asyncio 的超时机制
async def fetch_with_timeout(url, timeout=5):
try:
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=timeout) as response:
return await response.text()
except asyncio.TimeoutError:
print(f"请求超时: {url}")
return None
总结
-
多线程:适合I/O密集型,简单并行任务
-
多进程:适合CPU密集型,需要内存隔离的任务
-
协程:适合高并发I/O,网络应用
-
锁:保护共享资源,注意避免死锁
-
同步/异步:根据业务需求选择阻塞或非阻塞模型
选择正确的并发模型需要综合考虑任务类型、资源限制、开发复杂度等因素。在实际应用中,常常组合使用多种技术以达到最佳效果。