1. 多线程(Multithreading)
在 Python 中,threading模块提供了多线程编程的支持。多线程可以在同一进程内并发执行多个任务,提高程序的执行效率。
import threading
def print_numbers():
for i in range(1, 11):
print(f"Thread 1: {i}")
def print_letters():
for letter in 'abcdefghij':
print(f"Thread 2: {letter}")
thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_letters)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
但 Python 的多线程存在一个重要的难点 ——GIL(全局解释器锁)。
GIL 的概念:GIL 是 Python 解释器中的一个互斥锁,它确保在任何时刻,只有一个线程能够执行 Python 字节码。这意味着,即使在多核 CPU 的环境下,Python 的多线程也无法真正地并行执行,而只能并发执行。
GIL 的原理:Python 解释器是基于引用计数的内存管理机制,为了保证内存管理的正确性,GIL 被引入。当一个线程开始执行时,它会先获取 GIL,在执行过程中,每隔一定的字节码指令或者遇到 I/O 操作时,会释放 GIL,让其他线程有机会获取 GIL 并执行。
GIL 对多线程性能的影响:在 CPU 密集型任务中,由于线程大部分时间都在执行计算,很少释放 GIL,导致其他线程只能等待,无法充分利用多核 CPU 的优势,多线程的性能提升不明显甚至可能下降。例如,进行大量的数学计算任务时:
import threading
import time
def cpu_intensive_task():
result = 0
for i in range(100000000):
result += i
start_time = time.time()
threads = []
for _ in range(4):
t = threading.Thread(target=cpu_intensive_task)
threads.append(t)
t.start()
for t in threads:
t.join()
end_time = time.time()
print(f"Total time with multithreading: {end_time - start_time} seconds")
在这个例子中,使用多线程执行 CPU 密集型任务,实际运行时间可能比单线程执行还要长。
而在 I/O 密集型任务中,由于线程大部分时间都在等待 I/O 操作完成,会频繁释放 GIL,其他线程有更多机会获取 GIL 并执行,多线程可以显著提高程序的执行效率。比如进行文件读取操作:
import threading
import time
def io_intensive_task():
with open('test.txt', 'r') as f:
data = f.readlines()
start_time = time.time()
threads = []
for _ in range(4):
t = threading.Thread(target=io_intensive_task)
threads.append(t)
t.start()
for t in threads:
t.join()
end_time = time.time()
print(f"Total time with multithreading: {end_time - start_time} seconds")
这里使用多线程执行 I/O 密集型任务,运行时间会比单线程短很多。
应对 GIL 的方法:
- 使用多进程:如前面多进程部分所述,multiprocessing模块提供了多进程编程支持,每个进程有独立的 Python 解释器和内存空间,不存在 GIL 问题,适合 CPU 密集型任务。
- 使用线程池结合异步 I/O:对于 I/O 密集型任务,可以使用线程池管理线程,并结合异步 I/O 操作,进一步提高并发性能。例如concurrent.futures模块中的ThreadPoolExecutor。
import concurrent.futures
import asyncio
import aioht