并发编程:使用多线程与多进程提升 Python 程序性能
并发编程:使用多线程与多进程提升 Python 程序性能
随着计算机硬件的不断发展,单核 CPU 已逐渐成为过去,多核 CPU 已经成为主流。为了充分利用计算机的性能,我们需要使用 并发编程 来让程序执行得更快。Python 提供了多线程和多进程的支持,让开发者可以通过并发方式提升程序性能。
本篇文章将详细介绍 Python 并发编程的核心概念,包括 多线程 和 多进程 的区别、优缺点、适用场景,并提供多个实战示例,帮助你更好地理解和使用并发编程来优化程序性能。
一、并发编程的基本概念
1.1 并发与并行
1.2 多线程与多进程
GIL 限制
Python 的 GIL (Global Interpreter Lock) 是一把全局锁,用于确保同一时刻只有一个线程在执行 Python 字节码。由于 GIL 的存在,多线程在 CPU 密集型任务 中无法充分利用多核 CPU。
二、Python 多线程编程
2.1 使用 threading
模块
Python 提供了 threading
模块来创建和管理线程,适合 IO 密集型任务,例如网络请求、文件读写等。
示例:多线程下载多个网页
import threading
import requests
import time
# 要下载的网页列表
URLS = [
'https://www.example.com',
'https://www.python.org',
'https://www.github.com',
]
# 下载网页的函数
def download_page(url):
print(f"开始下载:{url}")
response = requests.get(url)
print(f"{url} 下载完成,长度:{len(response.text)}")
# 多线程实现
start_time = time.time()
threads = []
for url in URLS:
thread = threading.Thread(target=download_page, args=(url,))
thread.start()
threads.append(thread)
# 等待所有线程结束
for thread in threads:
thread.join()
end_time = time.time()
print(f"总耗时:{end_time - start_time:.2f} 秒")
说明:
threading.Thread
创建线程,通过 start()
启动线程。join()
确保主线程等待所有子线程执行完毕。适用场景:多线程适合 IO 密集型任务,如网络请求、文件读写等。
三、Python 多进程编程
3.1 使用 multiprocessing
模块
multiprocessing
模块允许在多个 CPU 核心上运行多个进程,可以绕过 Python 的 GIL,适合 CPU 密集型任务,如计算密集型操作。
示例:多进程计算密集型任务
假设我们需要计算一组数字的平方和:
import multiprocessing
import time
# 计算一组数字的平方和
def compute_square(numbers):
result = sum(x * x for x in numbers)
print(f"结果:{result}")
if __name__ == "__main__":
numbers = list(range(10000000))
num_processes = 4 # 进程数
chunk_size = len(numbers) // num_processes
chunks = [numbers[i:i + chunk_size] for i in range(0, len(numbers), chunk_size)]
# 启动多进程
start_time = time.time()
processes = []
for chunk in chunks:
process = multiprocessing.Process(target=compute_square, args=(chunk,))
process.start()
processes.append(process)
for process in processes:
process.join()
end_time = time.time()
print(f"总耗时:{end_time - start_time:.2f} 秒")
说明:
multiprocessing.Process
创建子进程,通过 start()
启动进程。join()
确保主进程等待所有子进程执行完毕。适用场景:多进程适合 CPU 密集型任务,如大规模计算、图像处理等。
四、线程池与进程池
对于大量任务,手动管理线程和进程可能会很麻烦。Python 提供了 线程池 和 进程池,用于批量管理线程和进程。
4.1 使用 concurrent.futures
的线程池
线程池会自动管理线程的创建与销毁,简化了多线程的使用。
from concurrent.futures import ThreadPoolExecutor
import requests
import time
URLS = [
'https://www.example.com',
'https://www.python.org',
'https://www.github.com',
]
def download_page(url):
response = requests.get(url)
print(f"{url} 下载完成,长度:{len(response.text)}")
start_time = time.time()
# 使用线程池
with ThreadPoolExecutor(max_workers=3) as executor:
executor.map(download_page, URLS)
end_time = time.time()
print(f"总耗时:{end_time - start_time:.2f} 秒")
说明:
ThreadPoolExecutor
管理线程池,通过 map
提交任务。max_workers
设置线程数量。4.2 使用 concurrent.futures
的进程池
进程池适用于 CPU 密集型任务。
from concurrent.futures import ProcessPoolExecutor
import time
def compute_square(n):
return sum(x * x for x in range(n))
if __name__ == "__main__":
numbers = [10**6, 10**6, 10**6, 10**6]
start_time = time.time()
# 使用进程池
with ProcessPoolExecutor(max_workers=4) as executor:
results = executor.map(compute_square, numbers)
print(list(results))
end_time = time.time()
print(f"总耗时:{end_time - start_time:.2f} 秒")
五、常见问题与排查技巧
5.1 GIL 限制
多线程在 Python 中受到 GIL 限制,导致无法充分利用多核 CPU。因此,对于 CPU 密集型任务,建议使用多进程代替多线程。
5.2 线程安全问题
在多线程编程中,多个线程共享同一块内存,可能导致数据竞争。解决方案:
threading.Lock
。queue.Queue
。5.3 进程间通信
多进程的内存是独立的,无法直接共享数据。可以通过以下方式通信:
multiprocessing.Queue
。multiprocessing.Pipe
。六、总结
threading
或线程池 ThreadPoolExecutor
)。multiprocessing
或进程池 ProcessPoolExecutor
)。concurrent.futures
,自动管理线程/进程,简化代码。通过合理地使用多线程和多进程,可以大幅提升 Python 程序的性能,特别是在处理大规模数据、网络请求或计算任务时。希望本文的介绍与示例能帮助你更好地理解并发编程并将其应用到实际项目中。
作者:全栈探索者chen