Python的multiprocessing
模块允许你创建和管理进程。
多进程是指同时运行多个进程,每个进程都有自己的Python解释器和内存空间。多进程适用于CPU密集型任务,因为每个进程有自己的Python解释器和内存空间,可以在不同的CPU核心上运行,不受GIL的限制。
关键特性
- 多核处理器的并行计算:由于每个进程有自己的Python解释器和内存空间,它们可以真正并行运行在多核处理器上。
- 避免GIL:不受全局解释器锁(GIL)的限制,适合CPU密集型任务。
- 资源共享:提供了多种方式来在进程间共享数据,例如
Queue
、Pipe
、Value
和Array
。 - 进程同步:提供了锁(
Lock
)、信号量(Semaphore
)、条件(Condition
)等同步原语。
import time
import multiprocessing
def worker(process_name):
print(f"{process_name} 开始工作")
time.sleep(2) # 模拟工作
print(f"{process_name} 完成工作")
if __name__ == "__main__":
# 创建进程
processes = []
for i in range(5):
process = multiprocessing.Process(target=worker, args=(f"进程-{i+1}",))
processes.append(process)
process.start()
# 等待所有进程完成
for process in processes:
process.join()
print("所有进程完成")
进程间通信
在Python中,进程间通信(IPC)是指不同进程之间交换数据的过程。由于Python的multiprocessing
模块创建的每个进程都有自己的内存空间,因此进程间不共享内存,需要使用特定的机制来实现通信。以下是multiprocessing
模块提供的几种进程间通信的方法:
1. Queue(队列)
Queue
是multiprocessing
模块中最常用的进程间通信方式,它提供了一个线程和进程安全的队列实现,可以用来在进程间传递消息或数据。
from multiprocessing import Process, Queue
def worker(q):
for i in range(5):
q.put(i)
print(f'Put {i} into queue')
if __name__ == '__main__':
q = Queue()
p = Process(target=worker, args=(q,))
p.start()
p.join()
while not q.empty():
print(f'Get {q.get()} from queue')
2. Pipe(管道)
Pipe
提供了一对连接,允许两个进程进行双向通信。每个连接可以用于发送和接收数据。
from multiprocessing import Process, Pipe
def worker(conn):
conn.send("Hello from worker")
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
p = Process(target=worker, args=(child_conn,))
p.start()
p.join()
print(f'Received: {parent_conn.recv()}')
parent_conn.close()
3. Value(共享值)
Value
允许多个进程共享一个简单的数值数据。它使用共享内存,因此进程可以直接访问这个值。
from multiprocessing import Process, Value
def worker(num):
num.value = 3.14
if __name__ == '__main__':
num = Value('d', 0.0)
p = Process(target=worker, args=(num,))
p.start()
p.join()
print(f'Value from main: {num.value}')
4. Array(共享数组)
Array
类似于Value
,但它用于共享一维数组。
from multiprocessing import Process, Array
def worker(arr):
for i in range(len(arr)):
arr[i] = i + 1
if __name__ == '__main__':
arr = Array('i', range(5))
p = Process(target=worker, args=(arr,))
p.start()
p.join()
print(f'Array from main: {arr[:]}')
- 数据类型:在使用
Value
和Array
时,需要指定正确的数据类型。- 同步:虽然
Queue
和Pipe
是线程安全的,但在使用时可能仍需要考虑同步问题,尤其是在多个进程同时访问同一个队列或管道时。- 内存管理:
Value
和Array
使用共享内存,这意味着它们在进程间共享数据时比通过序列化传递数据更高效。- 错误处理:在使用进程间通信时,应当妥善处理可能出现的错误,例如在
Queue
操作中可能会抛出的Full
和Empty
异常。
进程池
ProcessPoolExecutor
进程池可以通过concurrent.futures
模块中的ProcessPoolExecutor
类实现的。这个类提供了一个接口,允许你异步地执行可调用对象,并将任务分派到多个进程。
from concurrent.futures import ProcessPoolExecutor
def task(n):
return n * n
if __name__ == '__main__':
with ProcessPoolExecutor() as executor:
# submit方法提交任务并获取Future对象
future = executor.submit(task, 10)
# 等待任务完成并获取结果
result = future.result()
print(f"任务结果:{result}")
# 并发执行多个任务
results = executor.map(task, [1, 2, 3, 4, 5])
for result in results:
print(f"任务结果:{result}")
multiprocessing.Pool
multiprocessing
模块也提供了一个Pool
类,类似于concurrent.futures.ProcessPoolExecutor
,可以方便地并行执行多个任务。
from multiprocessing import Pool
def worker(x):
return x * x
if __name__ == '__main__':
with Pool(5) as p: # 创建一个包含5个进程的进程池
results = p.map(worker, range(10)) # 将任务分配给进程池中的进程
print(results)
多进程使用
- 安全启动:在使用
ProcessPoolExecutor
时,确保你的代码包含if __name__ == '__main__':
,以防止在Windows上无限递归创建子进程。 - 资源共享:由于进程间不共享内存,所以需要使用
multiprocessing
模块提供的机制来共享数据。 - 进程同步:当多个进程需要访问共享资源时,需要使用同步原语来避免竞态条件。
- 性能考虑:创建进程比创建线程成本更高,因为每个进程需要独立的内存空间。