Python multiprocessing多进程原理与应用示例
本文详解Python multiprocessing模块的多进程原理,涵盖Process对象生命周期管理、进程池Pool的使用、Queue消息队列通信及共享内存机制。通过具体代码示例展示如何高效利用多核CPU提升程序运行效率,并解析进程间变量共享与隔离的注意事项。
多进程基础原理
在Python中,multiprocessing包提供了跨平台的多进程管理功能。与多线程不同,多进程为每个进程分配了独立的内存空间,因此不存在全局解释器锁(GIL)争抢的问题。在多核CPU环境下,使用多进程可以充分利用硬件资源,显著提高程序的运行效率。
Process对象与生命周期
Process类是创建子进程的核心对象。其构造函数接受以下参数:
group:通常不使用,保留为None。target:指定子进程要执行的函数。name:定义进程的名称,便于调试和识别。args:以元组形式传递给目标函数的位置参数。kwargs:以字典形式传递给目标函数的关键字参数。
进程的生命周期由几个关键方法控制:
start():启动子进程,使其开始执行目标函数。join(timeout=None):阻塞主进程,直到子进程结束。可以设置超时时间,超时后主进程继续执行。terminate():强制终止子进程。调用后通常需要紧跟join()来更新进程状态。is_alive():检查进程是否仍在运行。
此外,daemon属性决定了子进程是否随主进程结束而终止。若设为True,子进程将随主进程退出而强制结束;若为False(默认值),子进程会独立运行直至完成。注意,daemon必须在调用start()之前设置。
进程状态与退出码
进程结束后,可以通过exitcode属性判断其退出状态:
0:正常退出,无错误。> 0:进程发生错误,以该退出码结束。< 0:进程被信号终止。
进程池Pool的使用
当需要管理大量子进程时,手动创建Process对象效率较低。此时可以使用Pool(进程池)来自动管理进程的生命周期和任务分配。
创建与任务提交
通过Pool()构造函数可以创建一个进程池,默认情况下,进程池中的进程数量等于机器的CPU核心数(可通过multiprocessing.cpu_count()获取)。当提交的任务数超过进程池容量时,进程池会自动复用已完成的进程来执行新任务。
使用apply_async()方法可以异步提交任务,该方法返回一个AsyncResult对象,可用于获取任务结果或检查状态。
进程池的生命周期管理
进程池的使用遵循严格的顺序:
- 提交所有任务。
- 调用
p.close():关闭进程池,禁止再提交新任务。 - 调用
p.join():阻塞主进程,等待所有子进程执行完毕。
注意,join()必须在close()之后调用。
进程间通信与共享
由于多进程拥有独立的内存空间,父进程与子进程之间无法直接共享普通变量。Python提供了多种机制来实现进程间通信(IPC)和共享内存。
Queue消息队列
multiprocessing.Queue是进程间通信的常用方式。它支持多线程和多进程安全地传递数据。
put(value, block=True, timeout=None):向队列添加值。可设置超时时间。get(block=True, timeout=None):从队列获取并移除值。可设置超时时间。empty():判断队列是否为空。full():判断队列是否已满。qsize():返回队列中消息的大致数量。
在进程池Pool中使用Queue时,必须使用Manager().Queue(),因为普通的Queue无法在进程池的子进程间正确共享。
共享内存对象
对于需要频繁读写的大型数据结构,可以使用共享内存对象来避免数据复制的开销。
multiprocessing.Value(typecode_or_type, value, lock=True):创建一个共享的基本类型变量(如整数、浮点数)。multiprocessing.Array(typecode_or_type, sequence, lock=True):创建一个共享的数组。
通过Value和Array,子进程可以直接修改这些对象,修改结果对父进程和其他子进程可见。
代码示例
1. 基础Process使用
import multiprocessing
import time
import cv2
def daemon1(image):
name = multiprocessing.current_process().name
for i in range(50):
image = cv2.GaussianBlur(image, (3, 3), 1)
time.sleep(0.1)
print 'daemon1 done!'
cv2.imshow('daemon1', image)
def daemon2(image):
name = multiprocessing.current_process().name
for i in range(50):
image = cv2.GaussianBlur(image, (3, 3), 1)
time.sleep(0.5)
print 'daemon2 done!'
cv2.imshow('daemon2', image)
if __name__ == '__main__':
t1 = time.time()
number_kernel = multiprocessing.cpu_count()
print 'We have {0} kernels'.format(number_kernel)
p1 = multiprocessing.Process(name='daemon1',
target=daemon1,args= (cv2.imread('./p1.jpg'),))
p1.daemon = False
p2 = multiprocessing.Process(name='daemon2',
target=daemon2, args=(cv2.imread('./p2.jpg'),))
p2.daemon = False
p1.start()
p2.start()
print 'p1 is {0}'.format(p1.is_alive())
p1.terminate()
p1.join()
print 'p1 is {0}'.format(p1.is_alive())
print 'p2 is {0}'.format(p2.is_alive())
p2.join()
t2 = time.time()
print '!!!!!!!!!!!!!!!!!!!!OK!!!!!!!!!!!!!!!!!!!!!'
print 'total time is {0}'.format(t2-t1)
print 'p1.exitcode = {0}'.format(p1.exitcode)
print 'p2.exitcode = {0}'.format(p2.exitcode)2. 进程池Pool使用
from multiprocessing import Pool
import os, time
def long_time_task(name):
print 'Run task {0} ({1})'.format(name,os.getpid())
start = time.time()
time.sleep(3)
end = time.time()
print 'Task {0} runs {1:.2f} seconds.'.format(name,end - start)
if __name__=='__main__':
print 'Parent process ({0})'.format(os.getpid)
p = Pool()
for i in range(12):
p.apply_async(long_time_task, args=(i,))
print 'Waiting for all subprocesses done...'
p.close()
p.join()
print 'All subprocesses done.'3. Queue消息队列通信
from multiprocessing import Process, Queue
import os, time, random
def write(q):
for value in ['A', 'B', 'C']:
print 'Put {0} to queue...'.format(value)
q.put(value)
time.sleep(random.random())
def read(q):
while True:
if not q.empty():
value = q.get(True)
print 'Get {0} from queue.'.format(value)
time.sleep(random.random())
else:
break
if __name__=='__main__':
q = multiprocessing.Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
pw.start()
pw.join()
pr.start()
pr.join()4. 进程池中使用Queue
from multiprocessing import Process, Queue
import os, time, random
def write(q):
for value in ['A', 'B', 'C']:
print 'Put {0} to queue...'.format(value)
q.put(value)
time.sleep(random.random())
def read(q):
while True:
if not q.empty():
value = q.get(True)
print 'Get {0} from queue.'.format(value)
time.sleep(random.random())
else:
break
if __name__=='__main__':
manager = multiprocessing.Manager()
q = manager.Queue()
p = Pool()
pw = p.apply_async(write, args=(q,))
time.sleep(2)
pr = p.apply_async(read, args=(q,))
p.close()
p.join()
if not q.empty():
print 'q is not empty...'
else:
print 'q is empty...'
print 'OK'
if not q.empty():
print 'q is not empty...'
else:
print 'q is empty...'
print 'done...'5. 普通变量与共享内存对比
以下示例展示了普通变量在子进程中修改后不会影响父进程,而使用Value和Array可以实现共享内存修改。
# 普通变量(不共享)
import multiprocessing
from multiprocessing import Pool
def changevalue(n, a):
n = 3.14
a[0] = 5
if __name__ == '__main__':
num = 0
arr = range(10)
p = Pool()
p1 = p.apply_async(changevalue, args=(num, arr))
p.close()
p.join()
print num
print arr[:]
# 共享内存(共享)
import multiprocessing
def changevalue(n, a):
n.value = 3.14
a[0] = 5
if __name__ == '__main__':
num = multiprocessing.Value('d', 0.0)
arr = multiprocessing.Array('i', range(10))
p = multiprocessing.Process(target=changevalue, args=(num, arr))
p.start()
p.join()
print num.value
print arr[:]
# 进程池中使用共享内存
import multiprocessing
from multiprocessing import Pool
def changevalue(n, a):
n.value = 3.14
a[0] = 5
if __name__ == '__main__':
num = multiprocessing.Value('d', 0.0)
arr = multiprocessing.Array('i', range(10))
p = Pool()
p1 = p.apply_async(changevalue, args=(num, arr))
p.close()
p.join()
print num.value
print arr[:]以上就是Python multiprocessing多进程原理与应用示例的详细内容,更多关于Python多进程编程的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













