多进程是指在操作系统中同时运行多个进程,每个进程都有自己独立的内存空间和系统资源。Python 的multiprocessing
模块提供了多进程编程的支持。
代码示例
import multiprocessing
# 定义一个简单的任务函数
def worker(num):
"""进程要执行的任务"""
# 打印当前工作进程的编号,表示该进程已开始执行任务
print(f'Worker {num} started')
# 计算当前工作进程编号的平方,作为任务的结果
result = num * num
# 打印当前工作进程的编号和任务结果,表示该进程已完成任务
print(f'Worker {num} finished with result: {result}')
# 返回任务结果
return result
if __name__ == '__main__':
# 创建一个进程池,最多同时运行3个进程
pool = multiprocessing.Pool(processes=3)
# 向进程池提交任务
results = [pool.apply_async(worker, args=(i,)) for i in range(5)]
# 关闭进程池,不再接受新的任务
pool.close()
# 等待所有进程完成任务
pool.join()
# 获取每个进程的返回结果
output = [p.get() for p in results]
# 打印最终结果,即所有进程执行任务后的返回结果
print(f'Final results: {output}')
multiprocessing.Pool
并不是最多只能同时运行 3 个进程,在创建 Pool
对象时,processes
参数指定了进程池中的进程数量,示例代码中设置为 3 只是一个示例,你可以根据实际需求调整该参数。
当你的 CPU 是 16 核时,你可以将 processes
参数设置为 16,这样进程池就会创建 16 个进程,理论上可以充分利用 CPU 的多核性能。不过,在实际应用中,还需要考虑系统资源的其他使用情况(如内存),有时候并不一定需要将进程数量设置得和 CPU 核心数完全一致。
下面是一个示例代码,展示了如何将 processes
参数设置为 16 来充分利用 16 核 CPU:
import multiprocessing
# 定义一个简单的任务函数,模拟 CPU 密集型任务
def worker(num):
"""进程要执行的任务"""
# 打印当前进程的开始信息
print(f'Worker {num} started')
# 模拟一些 CPU 密集型计算
# 初始化结果变量
result = 0
# 循环从0到999999
for i in range(1000000):
# 将当前循环变量i累加到结果变量result中
result += i
# 打印当前进程的完成信息
print(f'Worker {num} finished')
# 返回累加结果
return result
if __name__ == '__main__':
# 获取 CPU 的核心数
cpu_count = multiprocessing.cpu_count()
print(f"CPU 核心数: {cpu_count}")
# 创建一个进程池,进程数量设置为 CPU 核心数
pool = multiprocessing.Pool(processes=cpu_count)
# 向进程池提交任务,这里假设要执行 20 个任务
results = [pool.apply_async(worker, args=(i,)) for i in range(20)]
# 关闭进程池,不再接受新的任务
pool.close()
# 等待所有进程完成任务
pool.join()
# 获取每个进程的返回结果
output = [p.get() for p in results]
# 打印最终结果列表的长度
print(f'Final results length: {len(output)}')
代码解释
multiprocessing.cpu_count()
:用于获取当前系统的 CPU 核心数。multiprocessing.Pool(processes=cpu_count)
:创建一个进程池,进程数量设置为 CPU 核心数,这样可以充分利用多核 CPU 的性能。pool.apply_async(worker, args=(i,))
:向进程池异步提交任务,worker
是任务函数,args
是传递给任务函数的参数。pool.close()
:关闭进程池,不再接受新的任务。pool.join()
:等待所有进程完成任务。p.get()
:获取每个进程的返回结果。注意事项
在 Python 多进程编程中,由于每个进程都有自己独立的内存空间,进程间的通讯(Inter - Process Communication,IPC)和数据共享就变得尤为重要。下面说明几种常见的进程间通讯和数据共享的方式:
Queue
(队列)multiprocessing.Queue
是一个线程和进程安全的队列,可用于在多个进程间传递数据。
示例代码
import multiprocessing
def producer(queue):
# 生产者进程,向队列中放入数据
for i in range(5):
queue.put(i)
print(f"Produced {i}")
def consumer(queue):
# 消费者进程,从队列中取出数据
while True:
item = queue.get()
if item is None:
break
print(f"Consumed {item}")
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()
# 向队列中放入 None 作为结束信号
queue.put(None)
p2.join()
print("All processes finished.")
代码解释
multiprocessing.Queue()
:创建一个队列对象。queue.put(item)
:将数据 item
放入队列。queue.get()
:从队列中取出数据。Pipe
(管道)multiprocessing.Pipe
返回一对连接对象,可用于在两个进程间进行双向通讯。
示例代码
import multiprocessing
def sender(conn):
# 发送进程,向管道发送数据
messages = ["Hello", "World", "!"]
for message in messages:
conn.send(message)
print(f"Sent: {message}")
conn.close()
def receiver(conn):
# 接收进程,从管道接收数据
while True:
try:
message = conn.recv()
print(f"Received: {message}")
except EOFError:
break
conn.close()
if __name__ == '__main__':
# 创建一个管道,返回两个连接对象
parent_conn, child_conn = multiprocessing.Pipe()
# 创建发送进程
p1 = multiprocessing.Process(target=sender, args=(child_conn,))
# 创建接收进程
p2 = multiprocessing.Process(target=receiver, args=(parent_conn,))
p1.start()
p2.start()
p1.join()
p2.join()
print("All processes finished.")
代码解释
multiprocessing.Pipe()
:创建一个管道,返回两个连接对象 parent_conn
和 child_conn
。conn.send(data)
:向管道发送数据。conn.recv()
:从管道接收数据。Value
和 Array
multiprocessing.Value
和 multiprocessing.Array
可用于在多个进程间共享单个值或数组。
示例代码
import multiprocessing
def increment(counter):
# 增加共享值
for _ in range(1000):
with counter.get_lock():
counter.value += 1
if __name__ == '__main__':
# 创建一个共享的整数对象
counter = multiprocessing.Value('i', 0)
processes = []
# 创建多个进程
for _ in range(4):
p = multiprocessing.Process(target=increment, args=(counter,))
processes.append(p)
p.start()
for p in processes:
p.join()
print(f"Final counter value: {counter.value}")
代码解释
multiprocessing.Value('i', 0)
:创建一个共享的整数对象,初始值为 0。'i'
表示整数类型。counter.get_lock()
:获取锁,确保在修改共享值时不会发生数据竞争。counter.value
:访问共享值。Manager
multiprocessing.Manager
提供了一种更高级的方式来实现进程间的数据共享,它可以创建共享的列表、字典等对象。
示例代码
import multiprocessing
def worker(dictionary, key, value):
# 向共享字典中添加键值对
dictionary[key] = value
if __name__ == '__main__':
# 创建一个管理器对象
manager = multiprocessing.Manager()
# 创建一个共享的字典
shared_dict = manager.dict()
processes = []
# 创建多个进程
for i in range(5):
p = multiprocessing.Process(target=worker, args=(shared_dict, i, i * 2))
processes.append(p)
p.start()
for p in processes:
p.join()
print(f"Shared dictionary: {shared_dict}")
代码解释
multiprocessing.Manager()
:创建一个管理器对象。manager.dict()
:创建一个共享的字典对象。通过上述四种方式,你可以在 Python 多进程编程中实现进程间的通讯和数据共享。不同的方式适用于不同的场景,你可以根据具体需求选择合适的方法。
在现代计算机系统中,CPU 通常具有多个核心。然而,由于 Python 的全局解释器锁(GIL)的存在,多线程在处理 CPU 密集型任务时并不能充分发挥多核 CPU 的性能优势。而 multiprocessing
模块可以创建多个独立的进程,每个进程都有自己独立的 Python 解释器实例和内存空间,它们可以并行地在不同的 CPU 核心上运行,从而充分利用多核 CPU 的计算能力,显著提高程序在 CPU 密集型任务上的执行效率。
由于每个进程都是相互独立的,一个进程的崩溃不会影响其他进程的正常运行。如果在多线程编程中,某个线程出现了未处理的异常,可能会导致整个进程崩溃,从而影响其他线程的执行。而在多进程编程中,各个进程之间相互隔离,一个进程的异常只会导致该进程终止,其他进程可以继续正常工作,从而提高了程序的整体稳定性。
每个进程都有自己独立的内存空间,进程之间的数据是相互隔离的。这意味着在一个进程中对数据的修改不会影响其他进程中的数据,避免了多线程编程中常见的数据竞争和并发访问问题,提高了数据的安全性。当需要对数据进行并发处理时,只需将数据复制到各个进程中进行处理,处理完成后再进行汇总即可。
multiprocessing
模块提供了丰富的进程管理和通信机制,如 Process
类用于创建和管理进程,Queue
、Pipe
用于进程间通信,Value
、Array
和 Manager
用于进程间数据共享等。这些功能使得 multiprocessing
非常适合处理复杂的任务,例如分布式计算、大规模数据处理等。可以将一个复杂的任务分解为多个子任务,每个子任务由一个独立的进程来处理,通过进程间的通信和协作完成整个任务。
multiprocessing
模块的 API 设计简洁明了,易于学习和使用。它与 Python 的其他标准库和第三方库兼容性良好,可以方便地集成到现有的 Python 项目中。无论是小型脚本还是大型应用程序,都可以很容易地使用 multiprocessing
模块来实现多进程编程,提高程序的性能和并发处理能力。
multiprocessing
是 Python 标准库中用于实现多进程编程的模块,它在以下几种场景中能发挥最佳作用:
由于 Python 的全局解释器锁(GIL),多线程在处理 CPU 密集型任务时无法充分利用多核 CPU 的优势。而 multiprocessing
可以创建多个独立的进程,每个进程都有自己独立的 Python 解释器实例,能并行运行在不同的 CPU 核心上,显著提升计算效率。
适用场景举例
代码示例
import multiprocessing
def cpu_intensive_task(num):
result = 0
for i in range(1000000):
result += i
return result
if __name__ == '__main__':
pool = multiprocessing.Pool(processes=multiprocessing.cpu_count())
tasks = [i for i in range(10)]
results = pool.map(cpu_intensive_task, tasks)
pool.close()
pool.join()
print("All CPU - intensive tasks are done.")
当有大量相互独立的任务需要处理时,使用 multiprocessing
可以并行执行这些任务,从而大大缩短整体处理时间。
适用场景举例
代码示例
import multiprocessing
def process_file(file_path):
# 模拟文件处理操作
print(f"Processing {file_path}")
return file_path
if __name__ == '__main__':
file_paths = [f"file_{i}.txt" for i in range(20)]
pool = multiprocessing.Pool(processes=4)
results = pool.map(process_file, file_paths)
pool.close()
pool.join()
print("All file processing tasks are done.")
由于每个进程都是相互独立的,一个进程的崩溃不会影响其他进程的正常运行。因此,在对程序稳定性要求较高的场景中,multiprocessing
是一个很好的选择。
适用场景举例
对于一些复杂的任务,可以将其分解为多个子任务,每个子任务由一个独立的进程来处理,通过进程间的通信和协作完成整个任务。
适用场景举例
代码示例(简单的任务分解)
import multiprocessing
def subtask(task_id, result_queue):
# 模拟子任务处理
result = task_id * 2
result_queue.put(result)
if __name__ == '__main__':
result_queue = multiprocessing.Queue()
processes = []
for i in range(5):
p = multiprocessing.Process(target=subtask, args=(i, result_queue))
processes.append(p)
p.start()
for p in processes:
p.join()
final_results = []
while not result_queue.empty():
final_results.append(result_queue.get())
print("Final results:", final_results)
综上所述,multiprocessing
在 CPU 密集型任务、并行处理大量独立任务、提高程序稳定性以及复杂任务的并行分解等方面具有显著优势,是 Python 中实现高效并发编程的重要工具。
multiprocessing
模块虽然在很多场景下非常有用,但也存在一些不太适宜使用的情况,以下是具体介绍:
对于简单的 I/O 密集型任务,如少量的文件读写、简单的网络请求等,使用 multiprocessing
并不是一个好的选择,原因如下:
示例场景:一个脚本需要依次读取几个小文件的内容,使用单线程或多线程通常会比多进程更高效。
代码示例(不适合用多进程的情况):
import multiprocessing
def read_file(file_path):
with open(file_path, 'r') as f:
content = f.read()
return content
if __name__ == '__main__':
file_paths = ['file1.txt', 'file2.txt', 'file3.txt']
# 这里使用多进程处理简单文件读取,效率不高
pool = multiprocessing.Pool(processes=len(file_paths))
results = pool.map(read_file, file_paths)
pool.close()
pool.join()
print(results)
更好的做法是使用单线程:
def read_file(file_path):
with open(file_path, 'r') as f:
content = f.read()
return content
file_paths = ['file1.txt', 'file2.txt', 'file3.txt']
results = []
for file_path in file_paths:
results.append(read_file(file_path))
print(results)
在资源受限的环境中,如内存有限的嵌入式设备或运行在资源紧张的服务器上,使用 multiprocessing
可能会导致系统资源耗尽,出现以下问题:
当任务之间存在高度依赖关系,需要频繁进行数据交换和同步时,multiprocessing
可能不是最佳选择,因为:
示例场景:一个任务需要等待另一个任务的中间结果才能继续执行,并且这种交互非常频繁,使用多线程或异步编程可能更合适。
对于一些简单的脚本,开发者可能更注重代码的简洁性和易维护性。使用 multiprocessing
会引入进程管理、进程间通信等复杂的概念,增加代码的复杂度。在这种情况下,单线程或简单的多线程实现可能更符合需求。
示例:一个简单的脚本用于计算一组数字的总和,使用单线程代码会更简洁易懂:
numbers = [1, 2, 3, 4, 5]
total = sum(numbers)
print(total)
而使用多进程来实现会使代码变得复杂,且没有明显的性能提升。
spawn
,Linux 默认 fork
,确保代码在不同系统兼容。