深入理解Python多进程编程 multiprocessing
flyfish
Python 的 multiprocessing
模块允许创建多个进程,从而可以利用多核处理器的能力来并行执行任务。这意味着程序的不同部分可以在不同的CPU核心上同时运行,极大地提高了处理效率,特别是在执行计算密集型任务时。
与多线程相比,multiprocessing
使用的是系统级的进程而不是线程。每个进程都有独立的内存空间和系统资源,而线程则共享同一个进程的内存空间。因此,在Python中(特别是由于全局解释器锁GIL的存在),对于CPU密集型任务,使用multiprocessing
比多线程能更有效地利用多核CPU的优势。
进程的概念
在计算机操作系统中,进程是操作系统进行资源分配和调度的基本单位。一个进程可以包含多个线程。当使用multiprocessing
模块时,可以创建新的进程,这些新进程将与主程序并行运行,并且它们各自拥有独立的内存空间。
示例代码1:单个进程打印数字
下面是一个简单的示例,演示如何使用multiprocessing
模块创建一个进程来打印从1到5的数字:
import multiprocessing
import timedef print_numbers():"""打印从1到5的数字"""for i in range(1, 6):print("数字:", i)time.sleep(1) # 模拟耗时操作if __name__ == "__main__":# 创建一个新的进程process = multiprocessing.Process(target=print_numbers)# 启动进程process.start()# 等待进程完成process.join()
数字: 1
数字: 2
数字: 3
数字: 4
数字: 5
multiprocessing.Process()
:创建一个新的进程对象。target=print_numbers
:指定该进程的目标函数为print_numbers
。process.start()
:启动进程。process.join()
:等待进程结束。
示例代码2:两个进程分别打印不同字符串
下面是另一个示例,演示如何同时启动两个进程,每个进程打印不同的字符串:
import multiprocessingdef print_message(message):"""打印传入的消息"""print(f"消息: {message}")if __name__ == "__main__":# 创建两个进程process1 = multiprocessing.Process(target=print_message, args=("Hello from Process 1",))process2 = multiprocessing.Process(target=print_message, args=("Hello from Process 2",))# 启动两个进程process1.start()process2.start()# 等待两个进程都完成process1.join()process2.join()
消息: Hello from Process 1
消息: Hello from Process 2
在这个例子中,定义了一个print_message
函数,它接受一个字符串参数并打印出来。然后,创建了两个进程,每个进程都调用这个函数,但传递了不同的字符串参数。通过args
参数,可以向目标函数传递额外的参数。最后,启动这两个进程,并等待它们完成各自的执行。这样,就可以看到两个进程几乎同时开始工作,并打印出各自的消息。
示例3:使用 multiprocessing.Value
在多个进程中共享一个计数器
multiprocessing.Value
Value
允许多个进程共享一个值。它适用于需要在多个进程中共享简单数据类型(如整数或浮点数)的情况。
import multiprocessingdef increment(counter, lock):"""增加计数器的值"""for _ in range(1000):with lock:counter.value += 1if __name__ == "__main__":# 创建一个共享的整数值和锁counter = multiprocessing.Value('i', 0) # 'i' 表示整数类型lock = multiprocessing.Lock()# 创建多个进程来增加计数器processes = [multiprocessing.Process(target=increment, args=(counter, lock)) for _ in range(10)]# 启动所有进程for p in processes:p.start()# 等待所有进程完成for p in processes:p.join()print("最终计数器值:", counter.value)
最终计数器值: 10000
multiprocessing.Value(typecode_or_type, *args, lock=True)
:创建一个新的共享值对象。typecode_or_type
指定了要共享的数据类型(例如'i'
表示整数)。value.value
:访问共享值的实际内容。lock
:确保对共享资源的安全访问,防止竞态条件。
进程(Process)和线程(Thread)在Python中的区别
特性 | 进程(Process) | 线程(Thread) |
---|---|---|
内存空间 | 每个进程有独立的内存空间 | 所有线程共享同一进程的内存空间 |
资源消耗 | 开销较大,需要更多系统资源 | 轻量级,开销小,资源共享 |
通信难度 | 进程间通信复杂(IPC),如管道、套接字等 | 线程间通信简单,直接访问相同变量和数据结构 |
全局解释器锁(GIL) | 不受GIL限制,适合计算密集型任务 | 受GIL限制,对于计算密集型任务效率提升有限 |
适用场景 | 计算密集型任务,稳定性要求高的应用 | I/O密集型任务,快速响应用户界面的应用 |
崩溃影响 | 一个进程崩溃不影响其他进程 | 一个线程出错可能导致整个进程崩溃 |
Python中多线程(Thread)和多进程(Process)的区别
特性 | 多线程(Thread) | 多进程(Process) |
---|---|---|
内存空间 | 所有线程共享同一进程的内存空间 | 每个进程有独立的内存空间 |
资源消耗 | 轻量级,开销小,资源共享 | 开销较大,需要更多系统资源 |
通信难度 | 线程间通信简单,直接访问相同变量和数据结构 | 进程间通信复杂(IPC),如管道、套接字等 |
全局解释器锁 (GIL) | 受GIL限制,对于计算密集型任务效率提升有限 | 不受GIL限制,适合计算密集型任务 |
适用场景 | I/O密集型任务,快速响应用户界面的应用 | 计算密集型任务,稳定性要求高的应用 |
崩溃影响 | 一个线程出错可能导致整个进程崩溃 | 一个进程崩溃不影响其他进程 |
创建与销毁开销 | 创建和销毁开销较小 | 创建和销毁开销较大 |
并发性能 | 对于I/O密集型任务性能较好,但对于CPU密集型任务受限 | 对于CPU密集型任务性能较好 |
示例用途 | 网络请求、文件读写、GUI应用等 | 数据分析、图像处理、科学计算等 |
进程间通信
在Python的multiprocessing
模块中,提供了几种常用的进程间通信(IPC)方式,包括队列(Queue)、管道(Pipe)等。这些工具允许不同的进程之间安全地传递数据。
使用 multiprocessing.Queue
实现进程间通信
Queue
是一个线程和进程安全的 FIFO 队列,非常适合用于进程间的简单数据交换。
示例代码:
import multiprocessingdef producer(queue):"""生产者函数,向队列中添加数据"""for i in range(5):queue.put(f"数据 {i}")print(f"生产者放入: 数据 {i}")def consumer(queue):"""消费者函数,从队列中取出数据"""while not queue.empty():data = queue.get()print(f"消费者获取: {data}")if __name__ == "__main__":# 创建一个队列对象queue = multiprocessing.Queue()# 创建生产者和消费者进程p1 = multiprocessing.Process(target=producer, args=(queue,))p2 = multiprocessing.Process(target=consumer, args=(queue,))# 启动进程p1.start()p2.start()# 等待两个进程完成p1.join()p2.join()
生产者放入: 数据 0
生产者放入: 数据 1
生产者放入: 数据 2
生产者放入: 数据 3
生产者放入: 数据 4
消费者获取: 数据 0
消费者获取: 数据 1
消费者获取: 数据 2
消费者获取: 数据 3
消费者获取: 数据 4
- 队列的使用:
queue.put()
用于向队列中添加数据,queue.get()
用于从队列中取出数据。 - 数据传递原理:生产者进程通过调用
put
方法将数据放入队列,而消费者进程通过调用get
方法从队列中取出数据。Queue
对象是进程安全的,因此多个进程可以同时访问它而不发生冲突。
使用 multiprocessing.Pipe
实现进程间通信
Pipe
提供了一个双向通道,适用于两个进程之间的直接通信。
示例代码:
import multiprocessingdef sender(conn, messages):"""发送者函数,通过管道发送消息"""for msg in messages:conn.send(msg)print(f"发送者发送: {msg}")conn.close()def receiver(conn):"""接收者函数,通过管道接收消息"""while True:msg = conn.recv()if msg == "END":breakprint(f"接收者接收: {msg}")if __name__ == "__main__":# 创建一个管道对象parent_conn, child_conn = multiprocessing.Pipe()# 准备要发送的消息messages = ["Hello", "from", "sender", "END"]# 创建发送者和接收者进程p1 = multiprocessing.Process(target=sender, args=(child_conn, messages))p2 = multiprocessing.Process(target=receiver, args=(parent_conn,))# 启动进程p1.start()p2.start()# 等待两个进程完成p1.join()p2.join()
发送者发送: Hello
发送者发送: from
发送者发送: sender
发送者发送: END
接收者接收: Hello
接收者接收: from
接收者接收: sender
进程池的使用
multiprocessing.Pool
是一个用于管理一组工作进程的类,它可以简化并行任务的分配和结果收集。
示例代码:使用 Pool
并行计算数字的平方
import multiprocessingdef square(n):"""计算一个数的平方"""return n * nif __name__ == "__main__":# 定义要处理的数字列表numbers = [1, 2, 3, 4, 5]# 创建一个包含4个进程的进程池with multiprocessing.Pool(processes=4) as pool:# 使用map方法将square函数应用于每个数字results = pool.map(square, numbers)print("结果:", results)
结果: [1, 4, 9, 16, 25]
- 进程池的概念和作用:
Pool
允许你指定一定数量的工作进程,并且可以通过map
、apply
等方法轻松地将任务分配给这些进程。这样可以有效地利用多核CPU来加速计算密集型任务。 - 设置进程池大小:通过
processes
参数指定进程池中的工作进程数量,默认情况下,它会根据系统CPU核心数自动调整。 - 处理任务的方式:
pool.map()
方法类似于内置的map()
函数,但它会在多个进程中并行执行。在这个例子中,我们将square
函数应用到numbers
列表中的每个元素,并返回计算结果。
Semaphore(信号量)
信号量是一种更高级的同步机制,可以用来控制同时访问某一资源的进程数量。
示例:使用 Semaphore
控制并发访问
import multiprocessing
import timedef worker(semaphore, name):with semaphore:print(f"{name} 获得信号量")time.sleep(1)if __name__ == "__main__":semaphore = multiprocessing.Semaphore(3) # 最多允许3个进程同时访问processes = [multiprocessing.Process(target=worker, args=(semaphore, f"进程 {i}")) for i in range(6)]for p in processes:p.start()for p in processes:p.join()
Event(事件)
事件是一种简单的线程间通信机制,可以让一个或多个进程等待某个特定事件的发生。
示例:使用 Event
实现进程间的同步
import multiprocessing
import timedef wait_for_event(event):print("等待事件触发...")event.wait() # 阻塞直到事件被设置print("事件已触发!")def set_event(event):time.sleep(3)event.set() # 触发事件if __name__ == "__main__":event = multiprocessing.Event()p1 = multiprocessing.Process(target=wait_for_event, args=(event,))p2 = multiprocessing.Process(target=set_event, args=(event,))p1.start()p2.start()p1.join()p2.join()
Manager(管理器)
Manager
提供了更高层次的接口,可以创建可以在不同进程之间共享的数据结构,如列表、字典等。
示例:使用 Manager
创建共享数据结构
import multiprocessingdef append_to_list(shared_list, item):shared_list.append(item)print(f"添加到共享列表: {item}")if __name__ == "__main__":with multiprocessing.Manager() as manager:shared_list = manager.list() # 创建一个可共享的列表processes = [multiprocessing.Process(target=append_to_list, args=(shared_list, i)) for i in range(5)]for p in processes:p.start()for p in processes:p.join()print("最终共享列表:", list(shared_list))
文中processes = [multiprocessing.Process(target=append_to_list, args=(shared_list, i)) for i in range(5)]
这一句 等于下面的代码
processes = []
for i in range(5):p = multiprocessing.Process(target=append_to_list, args=(shared_list, i))processes.append(p)
共享内存
multiprocessing
还支持通过共享内存的方式在进程之间共享数据,这对于大规模数据共享特别有用。
示例:使用 Array
共享数组
import multiprocessingdef modify_array(shared_array, index, value):shared_array[index] = valueif __name__ == "__main__":array = multiprocessing.Array('i', [1, 2, 3, 4, 5]) # 创建共享数组processes = [multiprocessing.Process(target=modify_array, args=(array, i, i*10)) for i in range(len(array))]for p in processes:p.start()for p in processes:p.join()print("修改后的数组:", list(array))
修改后的数组: [0, 10, 20, 30, 40]