第7章:Python 并发与多线程编程
随着计算机硬件的发展,多核处理器已经成为主流。为了更好地利用多核资源,提高程序的运行效率,Python 提供了并发(Concurrency)和并行(Parallelism)编程的工具。本章将深入探讨 Python 中的 线程(Threading)、进程(Multiprocessing) 和 异步编程(Asyncio)。
7.1 并发与并行的区别
- 并发(Concurrency):多个任务交替执行,可能在单核 CPU 上完成,主要关注任务的切换。
- 并行(Parallelism):多个任务同时执行,必须依赖多核 CPU,多个任务真正“同时”运行。
简单理解:
- 并发 → 多个任务在同一时间段内“轮流”执行。
- 并行 → 多个任务在同一时刻“同时”执行。
7.2 多线程(Threading)
7.2.1 什么是线程?
- 线程(Thread) 是操作系统能够进行调度的最小单元。
- Python 提供了
threading
模块来实现多线程。 - 注意:由于 GIL(全局解释器锁) 的存在,Python 的多线程无法真正实现并行计算,适合 I/O 密集型任务。
7.2.2 使用 threading
模块
示例:创建多线程
import threading
import time
def task(name):
print(f"线程 {name} 开始")
time.sleep(2)
print(f"线程 {name} 结束")
# 创建线程
thread1 = threading.Thread(target=task, args=("线程1",))
thread2 = threading.Thread(target=task, args=("线程2",))
# 启动线程
thread1.start()
thread2.start()
# 等待线程结束
thread1.join()
thread2.join()
print("所有线程已完成")
输出:
线程 线程1 开始
线程 线程2 开始
线程 线程1 结束
线程 线程2 结束
所有线程已完成
7.2.3 线程锁(Lock)
多个线程同时访问共享资源时,可能会导致 数据竞争(Race Condition)。为了解决这个问题,可以使用 线程锁(Lock)。
import threading
lock = threading.Lock()
counter = 0
def increment():
global counter
with lock: # 上锁
temp = counter
temp += 1
counter = temp
threads = []
for i in range(5):
t = threading.Thread(target=increment)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"最终计数器值: {counter}")
7.3 多进程(Multiprocessing)
7.3.1 什么是进程?
- 进程(Process) 是操作系统资源分配的基本单位。
- Python 的
multiprocessing
模块支持真正的 并行计算,每个进程都有自己的 Python 解释器,不受 GIL 的限制。 - 适合 CPU 密集型任务。
7.3.2 使用 multiprocessing
模块
示例:创建多进程
import multiprocessing
import time
def task(name):
print(f"进程 {name} 开始")
time.sleep(2)
print(f"进程 {name} 结束")
if __name__ == "__main__":
process1 = multiprocessing.Process(target=task, args=("进程1",))
process2 = multiprocessing.Process(target=task, args=("进程2",))
process1.start()
process2.start()
process1.join()
process2.join()
print("所有进程已完成")
7.3.3 进程池(Pool)
当需要大量进程时,可以使用 进程池(Pool) 管理。
from multiprocessing import Pool
def square(n):
return n * n
if __name__ == "__main__":
with Pool(4) as pool:
results = pool.map(square, [1, 2, 3, 4, 5])
print(results) # [1, 4, 9, 16, 25]
7.4 异步编程(Asyncio)
7.4.1 什么是异步编程?
- 异步编程是一种 非阻塞 的编程方式。
- 使用
async
和await
关键字。 - 适合 I/O 密集型任务,例如网络请求、数据库访问等。
7.4.2 异步函数
示例:异步任务
import asyncio
async def task(name):
print(f"任务 {name} 开始")
await asyncio.sleep(2) # 异步等待
print(f"任务 {name} 结束")
async def main():
await asyncio.gather(
task("任务1"),
task("任务2")
)
asyncio.run(main())
输出:
任务 任务1 开始
任务 任务2 开始
任务 任务1 结束
任务 任务2 结束
7.4.3 异步与协程
async
:定义异步函数。await
:在异步函数内部暂停执行,等待某个异步任务完成。
7.5 并发工具
7.5.1 队列(Queue)
Python 提供了 queue.Queue
和 multiprocessing.Queue
来实现线程间或进程间通信。
import queue
import threading
q = queue.Queue()
def producer():
for i in range(5):
q.put(i)
print(f"生产者放入: {i}")
def consumer():
while not q.empty():
item = q.get()
print(f"消费者取出: {item}")
thread1 = threading.Thread(target=producer)
thread2 = threading.Thread(target=consumer)
thread1.start()
thread1.join()
thread2.start()
thread2.join()
7.6 小结
线程(Threading)
- 适合 I/O 密集型任务。
- 受限于 GIL,无法实现真正的并行。
进程(Multiprocessing)
- 适合 CPU 密集型任务。
- 每个进程都有独立的内存空间。
异步编程(Asyncio)
- 适合 I/O 密集型任务。
- 使用
async
和await
关键字实现。
7.7 实战项目:爬虫示例
使用多线程进行网页爬取
import threading
import requests
def fetch_url(url):
response = requests.get(url)
print(f"{url}: {response.status_code}")
urls = ["https://www.example.com", "https://www.python.org"]
threads = []
for url in urls:
t = threading.Thread(target=fetch_url, args=(url,))
threads.append(t)
t.start()
for t in threads:
t.join()