手册:multiprocessing — 基于进程的并行
Python multiprocessing模块中常用函数和类的统计
(对于进程间通信此处理解不清,可能有误)
进程间通信方式:
消息机制 (Pipe、Queue)
共享文件
同步原语(进程锁, 信号量 , 事件 )
这些有锁的功能的东西(能够阻塞)是全局的,加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
多进程应该尽可能避免在进程间传递大量数据,越少越好。
multiprocessing的Lock、RLock、Event、Semaphore、BoundedSemaphore、Condition接口与threading接口相同。
threading中的Lock、RLock、Event、Semaphore、BoundedSemaphore、Condition详解
multiprocessing.Queue()的接口与用于多线程的queue.Queue()类似。queue.Queue()比 multiprocessing.Queue()多两个方法task_done和join。multiprocessing.JoinableQueue()接口与queue.Queue()接口相同。
queue模块详解
使用场景:Pipe、Queue只适用于多个进程都是源于同一个父进程的情况。如果多个进程不是源于同一个父进程,只能用共享内存,信号量等方式,但是这些方式对于复杂的数据结构,例如Queue,dict,list等,使用起来比较麻烦,不够灵活,这种情况使用Manager。
使用方法:
import multiprocessing
def sub(a, lock):
print(f"sub_start;{a.value}")
# lock.acquire() # 对于该程序来说在此处获取锁效率更高,避免多次进程间通信
for i in range(100000):
lock.acquire()
b = a.value
b -= 1
a.value = b
lock.release()
# lock.release()
print(f"sub_end;{a.value}")
def add(a, lock):
print(f"add_start;{a.value}")
for i in range(100000):
lock.acquire()
b = a.value
b += 1
a.value = b
lock.release()
print(f"add_end;{a.value}")
if __name__ == '__main__':
a = multiprocessing.Value("i", 0) # a 属于共享内存中的变量
lock = multiprocessing.Lock() # lock也属于共享内存中的变量
tasks = [multiprocessing.Process(target=add, args=(a, lock), name="add"), multiprocessing.Process(target=sub, args=(a, lock), name="sub")]
[task.start() for task in tasks]
[task.join() for task in tasks]
print(a.value)
"""
运行结果:
add_start;0
sub_start;5011
add_end;3511
sub_end;0
0
Process finished with exit code 0
"""
1.2 补充:multiprocessing.Value()
1.2.1 Value的构造函数:
Value的初始化非常简单,直接类似Value(‘d’, 0.0)即可,具体构造方法为:
multiprocessing.Value(typecode_or_type, *args[, lock])。
该方法返回从共享内存中分配的一个ctypes 对象,其中typecode_or_type定义了返回的类型。它要么是一个ctypes类型,要么是一个代表ctypes类型的code。比如c_bool和’b’是同样的,因为’b’是c_bool的code。
ctypes是Python的一个外部函数库,它提供了和C语言兼任的数据类型,可以调用DLLs或者共享库的函数,能被用作在python中包裹这些库。
*args是传递给ctypes的构造参数
1.2.2 Value的使用
对于共享整数或者单个字符,初始化比较简单,参照下图映射关系即可。
注意,如果我们使用的code在上表不存在,则会抛出:
size = ctypes.sizeof(type_)
TypeError: this type has no size
如果共享的是字符串,则在上表是找不到映射关系的,就是没有code可用。所以我们需要使用原始的ctype类型
from multiprocessing import Value
from ctypes import *
a = Value("i", 2)
print(a.value)
print(a)
a = Value(c_int, 2)
print(a.value)
print(a)
a = Value("f", 3.14)
print(a.value)
print(a)
a = Value(c_float, 3.14)
print(a.value)
print(a)
a = Value('c', b'a')
print(a.value.decode("ascii"))
print(a)
a = Value(c_char, b'a')
print(a.value.decode("ascii"))
print(a)
a = Value(c_char_p, bytes("中国", "utf-8"))
print(a.value.decode("utf-8"))
print(a)
"""
运行结果:
2
2
3.140000104904175
3.140000104904175
a
a
中国
Process finished with exit code 0
"""
参考博客
import multiprocessing
def fibonacci(num, total, lock):
if num == 2 or num == 1:
return 1
else:
lock.acquire()
print("lock已为total上锁")
total.value = fibonacci(num - 1, total, lock) + fibonacci(num - 2, total, lock)
lock.release()
print("lock已为total释放锁")
return total.value
if __name__ == '__main__':
total = multiprocessing.Value("i", 0)
lock = multiprocessing.RLock()
task = multiprocessing.Process(target=fibonacci, args=(6, total, lock))
task.start()
task.join()
print(total.value)
"""
运行结果:
lock已为total上锁
lock已为total上锁
lock已为total上锁
lock已为total上锁
lock已为total释放锁
lock已为total释放锁
lock已为total上锁
lock已为total释放锁
lock已为total释放锁
lock已为total上锁
lock已为total上锁
lock已为total释放锁
lock已为total释放锁
lock已为total释放锁
8
Process finished with exit code 0
"""
import multiprocessing
import time
def test0(event):
event.clear()
print(f"{multiprocessing.current_process().name}_start========")
time.sleep(1)
print(f"{multiprocessing.current_process().name}_end========")
event.set()
def test1(event):
event.wait()
print(f"{multiprocessing.current_process().name}_end========")
if __name__ == '__main__':
event = multiprocessing.Event()
task0 = multiprocessing.Process(target=test0, args=(event,), name="Pro_test0")
task1 = multiprocessing.Process(target=test1, args=(event,), name="Pro_test0")
task0.start()
task1.start()
task0.join()
task1.join()
"""
运行结果:
Pro_test0_start========
Pro_test0_end========
Pro_test0_end========
Process finished with exit code 0
"""
import multiprocessing
import time
import random
def db_connect(num, semaphore):
print(f"thread =={num}== is preparing to connect to db.")
semaphore.acquire()
time.sleep(int(random.random()*10)/10) # 模拟连接db的过程
print(f"connection =={num}== already complete.")
semaphore.release()
if __name__ == '__main__':
semaphore = multiprocessing.Semaphore(value=2)
tasks = [multiprocessing.Process(target=db_connect, args=(num, semaphore)) for num in range(7)]
[task.start() for task in tasks]
[task.join() for task in tasks]
semaphore.release() # Semaphore对象释放多次不会抛出异常
"""
运行结果:
thread ==0== is preparing to connect to db.
thread ==1== is preparing to connect to db.
connection ==0== already complete.
thread ==2== is preparing to connect to db.
connection ==1== already complete.
connection ==2== already complete.
thread ==4== is preparing to connect to db.
thread ==3== is preparing to connect to db.
connection ==3== already complete.
thread ==5== is preparing to connect to db.
thread ==6== is preparing to connect to db.
connection ==4== already complete.
connection ==6== already complete.
connection ==5== already complete.
Process finished with exit code 0
"""
import multiprocessing
import time
def task_1(condition_obj):
proc_name = multiprocessing.current_process().name
print('开始 %s' % proc_name)
with condition_obj:
print('%s运行结束,开始运行task_2' % proc_name)
condition_obj.notify_all()
def task_2(condition_obj):
proc_name = multiprocessing.current_process().name
print('开始 %s' % proc_name)
with condition_obj:
condition_obj.wait()
print('task_2 %s 运行结束' % proc_name)
if __name__ == '__main__':
condition_obj = multiprocessing.Condition()
s1 = multiprocessing.Process(name='s1', target=task_1, args=(condition_obj,))
s2_clients = [multiprocessing.Process(name='task_2[{}]'.format(i), target=task_2, args=(condition_obj,),) for i in range(1, 3)]
for c in s2_clients:
c.start()
time.sleep(1)
s1.start()
s1.join()
for c in s2_clients:
c.join()
"""
运行结果:
开始 task_2[1]
开始 task_2[2]
开始 s1
s1运行结束,开始运行task_2
task_2 task_2[2] 运行结束
task_2 task_2[1] 运行结束
Process finished with exit code 0
"""
Pipe()函数返回两个对象 conn1 和 conn2 ,这两个对象表示管道的两端。
Pipe()函数有一个可选参数 duplex,参数 duplex 的默认值为 True,表示该管道是双向的,即两个对象都可以发送和接收消息。如果把参数 duplex 设置为 False ,表示该管道是单向的,即 conn1 只能用于接收消息,conn2 只能用于发送消息。
# 双向管道
import multiprocessing
def test0(conn):
conn.send("test1,我是test0")
print(conn.recv())
def test1(conn):
print(conn.recv())
conn.send("test0,你好")
if __name__ == '__main__':
conn0, conn1 = multiprocessing.Pipe(duplex=True)
task0 = multiprocessing.Process(target=test0, args=(conn0,))
task1 = multiprocessing.Process(target=test1, args=(conn1,))
task0.start()
task1.start()
task0.join()
task1.join()
"""
运行结果:
test1,我是test0
test0,你好
Process finished with exit code 0
"""
# 单向管道
import multiprocessing
def test0(conn):
# conn.send("test1,我是test0")
print(conn.recv())
def test1(conn):
# print(conn.recv())
conn.send("test0,你好")
if __name__ == '__main__':
conn0, conn1 = multiprocessing.Pipe(duplex=False) # conn0接收消息,conn1发送消息
task0 = multiprocessing.Process(target=test0, args=(conn0,))
task1 = multiprocessing.Process(target=test1, args=(conn1,))
task0.start()
task1.start()
task0.join()
task1.join()
"""
运行结果:
test0,你好
Process finished with exit code 0
"""
原型:Queue([maxsize])
参数介绍
当一个队列为空的时候如果再用get取则会堵塞,所以取队列的时候一般是用到get_nowait()方法,这种方法在向一个空队列取值的时候会抛一个Empty异常,所以更常用的方法是先判断一个队列是否为空,如果不为空则取值。
队列Queue的实例对象queue中常用的方法
queue.qsize()
返回队列的大小queue.empty()
如果队列为空,返回True,反之Falsequeue.full()
如果队列满了,返回True,反之Falsequeue.get([block[, timeout]])
获取队列,timeout等待时间queue.get_nowait()
相当Queue.get(False) ,非阻塞 Queue.put(item) 写入队列,timeout等待时间queue.put_nowait(item)
相当Queue.put(item, False)import multiprocessing
import time
def test0(queue, num):
for i in range(num*3):
queue.put(f"{num}: {i}")
def test1(queue):
flag = 0
while True:
if not queue.empty():
print(queue.get())
flag = 0
else:
time.sleep(0.1)
flag += 1
if flag == 2:
break
if __name__ == '__main__':
queue = multiprocessing.Queue()
task0 = [multiprocessing.Process(target=test0, args=(queue, num)) for num in range(1, 3)]
task1 = multiprocessing.Process(target=test1, args=(queue,))
[task.start() for task in task0]
task1.start()
[task.join() for task in task0]
task1.join()
"""
运行结果:
1: 0
1: 1
1: 2
1: 3
1: 4
2: 0
2: 1
2: 2
2: 3
2: 4
2: 5
2: 6
2: 7
2: 8
2: 9
Process finished with exit code 0
"""
原型:JoinableQueue([maxsize])
参数介绍
当一个队列为空的时候如果再用get取则会堵塞,所以取队列的时候一般是用到get_nowait()方法,这种方法在向一个空队列取值的时候会抛一个Empty异常,所以更常用的方法是先判断一个队列是否为空,如果不为空则取值。
JoinableQueue的实例queue队列中常用的方法:
queue.qsize()
返回队列的大小queue.empty()
如果队列为空,返回True,反之Falsequeue.full()
如果队列满了,返回True,反之Falsequeue.get([block[, timeout]])
获取队列,timeout等待时间queue.get_nowait()
相当Queue.get(False) ,非阻塞 Queue.put(item) 写入队列,timeout等待时间queue.put_nowait(item)
相当Queue.put(item, False)queue.task_done()
:使用者使用此方法发出信号,表示queue.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常queue.join()
:生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用queue.task_done()方法为止import multiprocessing
import time
def test0(queue, num):
for i in range(num*3):
queue.put(f"{num}: {i}")
def test1(queue):
flag = 0
while True:
if not queue.empty():
print(queue.get())
queue.task_done()
flag = 0
else:
time.sleep(0.1)
flag += 1
if flag == 2:
break
if __name__ == '__main__':
queue = multiprocessing.JoinableQueue(maxsize=1)
task0 = [multiprocessing.Process(target=test0, args=(queue, num)) for num in range(1, 3)]
task1 = multiprocessing.Process(target=test1, args=(queue,))
[task.start() for task in task0]
task1.start()
# [task.join() for task in task0]
# task1.join()
queue.join()
"""
运行结果:
1: 0
1: 1
2: 0
1: 2
2: 1
2: 2
2: 3
2: 4
2: 5
Process finished with exit code 0
"""
Pool类可以提供指定数量的进程供用户调用,当有新的请求提交到Pool中时,如果池还没有满,就会创建一个新的进程来执行请求。如果池满,请求就会告知先等待,直到池中有进程结束,才会创建新的进程来执行这些请求
下面介绍一下multiprocessing 模块下的Pool类下的几个方法:
apply(func[, args=()[, kwds={}]])
apply_async(func[, args=()[, kwds={}[, callback=None]]])
map(func, iterable[, chunksize=None])
map_async(func, iterable[, chunksize[, callback]])
close()
terminal()
join()
服务器进程
,该进程保存Python对象并允许其他进程使用代理操作它们。灵活
,因为它支持Python支持的所有数据类型。不同计算机上
的进程共享。但是,它们比使用共享内存慢
。原理:先启动一个ManagerServer进程,这个进程是阻塞
的,它监听一个socket
,然后其他进程(ManagerClient)通过socket来连接到ManagerServer,实现通信。
Manager()的Lock、RLock、Event、Semaphore、BoundedSemaphore、Condition接口与threading接口相同。
threading中的Lock、RLock、Event、Semaphore、BoundedSemaphore、Condition详解
Manager()的Queue、Value、Array的接口与multiprocess的Queue、Value、Array相同。
因此此处只谈接口list、dict、Namespace接口。
使用场景:
使用方法:
2.3.1 list、dict
# list
import multiprocessing
def add(temp_list, lock):
print(f"++++add_{multiprocessing.current_process().ident}_start:{temp_list[0]}++++")
for i in range(10000):
lock.acquire()
temp_list[0] += 1
lock.release()
print(f"++++add_{multiprocessing.current_process().ident}_end:{temp_list[0]}++++")
def sub(temp_list, lock):
print(f"++++sub_{multiprocessing.current_process().ident}_start:{temp_list[0]}++++")
for i in range(10000):
lock.acquire()
temp_list[0] -= 1
lock.release()
print(f"++++sub_{multiprocessing.current_process().ident}_end:{temp_list[0]}++++")
if __name__ == '__main__':
manager = multiprocessing.Manager()
temp_list = manager.list([0])
lock = manager.Lock()
pool = multiprocessing.Pool(2)
for num in range(2):
pool.apply_async(add, args=(temp_list, lock))
for num in range(2):
pool.apply_async(sub, args=(temp_list, lock))
pool.close()
pool.join()
print(temp_list)
"""
运行结果:
++++add_14680_start:0++++
++++add_9740_start:20++++
++++add_14680_end:19983++++
++++sub_14680_start:19991++++
++++add_9740_end:19992++++
++++sub_9740_start:19982++++
++++sub_14680_end:15++++
++++sub_9740_end:0++++
[0]
Process finished with exit code 0
"""
# dict
import multiprocessing
def add(temp_dict, lock):
print(f"++++add_{multiprocessing.current_process().ident}_start:{temp_dict['test']}++++")
for i in range(10000):
lock.acquire()
temp_dict['test'] += 1
lock.release()
print(f"++++add_{multiprocessing.current_process().ident}_end:{temp_dict['test']}++++")
def sub(temp_dict, lock):
print(f"++++sub_{multiprocessing.current_process().ident}_start:{temp_dict['test']}++++")
for i in range(10000):
lock.acquire()
temp_dict['test'] -= 1
lock.release()
print(f"++++sub_{multiprocessing.current_process().ident}_end:{temp_dict['test']}++++")
if __name__ == '__main__':
manager = multiprocessing.Manager()
temp_dict = manager.dict({'test': 0})
lock = manager.Lock()
pool = multiprocessing.Pool(2)
for num in range(2):
pool.apply_async(add, args=(temp_dict, lock))
for num in range(2):
pool.apply_async(sub, args=(temp_dict, lock))
pool.close()
pool.join()
print(temp_dict['test'])
"""
运行结果:
++++add_11360_start:0++++
++++add_476_start:17++++
++++add_476_end:19990++++
++++sub_476_start:19998++++
++++add_11360_end:19999++++
++++sub_11360_start:19991++++
++++sub_476_end:13++++
++++sub_11360_end:0++++
0
Process finished with exit code 0
"""
补充list、dict
Manager处理list、dict等可变数据类型时,需要注意一个陷阱,即Manager对象无法监测到它引用的可变对象值的修改,需要通过触发__setitem__方法来让它获得通知。
而触发__setitem__方法比较直接的办法就是增加一个中间变量,如同在C语言中交换两个变量的值一样:
int a=1;int b=2;int tmp=a;a=b;b=tmp;
示例:
# Manager对象无法监测到它引用的可变对象值的修改
import multiprocessing
def test(idx, test_dict, lock):
lock.acquire()
test_dict['test'][idx] = idx
lock.release()
if __name__ == '__main__':
manager = multiprocessing.Manager()
temp_dict = manager.dict()
lock = manager.Lock()
temp_dict['test'] = {}
pool = multiprocessing.Pool(4)
for i in range(10):
pool.apply_async(test, args=(i, temp_dict, lock))
pool.close()
pool.join()
print(temp_dict)
"""
运行结果:
{'test': {}}
Process finished with exit code 0
"""
# 通过触发__setitem__方法来让Manager对象获得通知
import multiprocessing
def test(idx, test_dict, lock):
lock.acquire()
row = test_dict['test']
row[idx] = idx
test_dict['test'] =row
lock.release()
if __name__ == '__main__':
manager = multiprocessing.Manager()
temp_dict = manager.dict()
lock = manager.Lock()
temp_dict['test'] = {}
pool = multiprocessing.Pool(4)
for i in range(10):
pool.apply_async(test, args=(i, temp_dict, lock))
pool.close()
pool.join()
print(temp_dict)
"""
运行结果:
{'test': {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 6: 6, 7: 7, 5: 5, 8: 8, 9: 9}}
Process finished with exit code 0
"""
2.3.2 Namespace
Namespace对象没有公共的方法,但是有可写的属性。Namespace支持python中所有的基本数据类型,但与dict、list相同Manager对象无法监测到它引用的可变对象值的修改,需要通过触发__setitem__方法来让它获得通知。
当使用manager返回的namespace的proxy的时候,_属性值属于proxy,跟原来的namespace没有关系。
import multiprocessing
def f(ns):
ns.x *= 10
ns.y *= 10
# ns.l[0] *= 10 # Manager对象无法监测到它引用的可变对象值的修改,需要通过触发__setitem__方法来让它获得通知。
l = ns.l
l[0] *= 10
ns.l = l
if 'testkey' in ns.d:
v = ns.d['testkey'] * 10
ns.d = {'testkey': v}
if __name__ == '__main__':
manager = multiprocessing.Manager()
ns = manager.Namespace()
ns.x = 1
ns.y = 2
ns._z = 5 # this is an attribute of the proxy
ns.d = {'testkey': 3}
ns.l = [4]
print('before', ns)
p = multiprocessing.Process(target=f, args=(ns,))
p.start()
p.join()
print('after', ns)
"""
运行结果:
before Namespace(d={'testkey': 3}, l=[4], x=1, y=2)
after Namespace(d={'testkey': 30}, l=[40], x=10, y=20)
Process finished with exit code 0
"""
[参考博客]
Python进程池multiprocessing.Pool的用法
python 多进程共享全局变量之Manager()详解
python 关于multiprocessing中在Namespace的实例下保存dict/list的疑问