目录
多进程访问冲突
多进程通信
mp.Queue
mp.Pipe
Value\Array
Manager共享Value、Array、dict、list、Lock、Semaphore等
mp.Manager共享自定义类的实例对象
Python进程池
参考
上接Python多线程 ,简单记录一下多进程中遇到的问题,本文中将multiprocessing简写为mp。
当多个进程需要访问共享资源时,需要避免访问的冲突。
冲突示例,抢占式访问,屏幕会乱序打印:
import multiprocessing as mp
import time
def add(number, change_number, lock):
# with lock:
for i in range(5):
number += change_number
print("add {0}. The number is {1}".format(change_number, number))
# 如果没有sleep语句,那么代码将很快执行完成,看不到多进程阻塞屏幕的现象
time.sleep(1)
print(number)
if __name__ == '__main__':
init_number = 0
process_lock = mp.Lock()
p1 = mp.Process(target=add, args=(init_number, 1, process_lock))
p2 = mp.Process(target=add, args=(init_number, 3, process_lock))
p1.start()
# p1.join()
p2.start()
# p2.join()
解决方法:
import multiprocessing as mp
import time
def add(number, change_number, lock):
with lock:
for i in range(5):
number += change_number
print("add {0}. The number is {1}".format(change_number, number))
# 如果没有sleep语句,那么代码将很快执行完成,看不到多进程抢占屏幕的现象
time.sleep(1)
print(number)
if __name__ == '__main__':
init_number = 0
process_lock = mp.Lock()
p1 = mp.Process(target=add, args=(init_number, 1, process_lock))
p2 = mp.Process(target=add, args=(init_number, 3, process_lock))
p1.start()
p2.start()
多进程可以充分利用cpu的多核特性,但是需要自己手动增加代码去实现进程间通信。要注意的一点是Python的mp模块,在windows和unix/Linux系统的实现方式有所不同。 在Unix/Linux下,multiprocessing模块封装了fork()调用,是我们不需要关注fork()的细节。由于windows没有fork调用,因此,multiprocessing需要“模拟”出fork的效果,父进程所有Python对象都必须通过pickle序列号再传到子进程中去。父进程和子进程之间通过序列化和反序列化来进行数据传递,要注意这里说的数据传递。个人认为Python的进程间通信分为两种,一种是数据传递,一种是数据共享。数据传递,例如mp.Pipe(),mp.Queue()。以mp.Queue()为例,表面上看,子进程和父进程共用一个queue,实际上并不是这样,而是子进程克隆了一个父进程的queue,子进程将数据放入克隆queue中,克隆queue将其序列化保存,然后进行反序列化后放到父进程的原始queue中,所以严格意义上子进程和父进程的queue并不是一个共享queue。所以多进程代码在Windows下执行时可能会报错:TypeError: can't pickle _thread.lock objects。这种问题可以考虑将代码放到Linux系统去执行。mp.Manager实现了数据共享。
from multiprocessing import Process, Queue # 引入进程queue
def f(q):
q.put([42, None, 'hello']) # 子进程放入数据
if __name__ == '__main__':
q = Queue()
p = Process(target=f, args=(q,)) # 将q传递给子进程
p.start()
print(q.get()) # 主进程取出数据
multiprocessing.Pipe()用来创建管道,返回两个连接对象,代表管道的两端,一般用于进程或者线程之间的通信,不
同于os.pipe(),os.pipe()主要用来创建两个文件描述符,一个读,一个写,是单向的。而multiprocessing.Pipe()则可以双向通信。
from multiprocessing import Process, Pipe
def f(conn):
conn.send([42, None, 'hello'])
print('from parent:', conn.recv())
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
p = Process(target=f, args=(child_conn,))
p.start()
print('from son:', parent_conn.recv())
parent_conn.send('hello')
p.join()
import multiprocessing
# Value/Array
def func1(a, arr):
a.value = 3.14
for i in range(len(arr)):
arr[i] = -arr[i]
if __name__ == '__main__':
num = multiprocessing.Value('d', 1.0) # num=0
arr = multiprocessing.Array('i', range(10)) # arr=range(10)
p = multiprocessing.Process(target=func1, args=(num, arr))
p.start()
p.join()
print(num.value)
print(arr[:])
Manager管理的共享数据类型有:Value、Array、dict、list、Lock、Semaphore等等,同时Manager还可以共享类的实例对象。
from multiprocessing import Process, Manager
def func1(share_list, share_value, share_dict, lock):
with lock:
share_value.value += 1
share_dict[1] = '1'
share_dict[2] = '2'
for i in range(len(share_list)):
share_list[i] += 1
if __name__ == '__main__':
manager = Manager()
list1 = manager.list([1, 2, 3, 4, 5])
dict1 = manager.dict()
array1 = manager.Array('i', range(10))
value1 = manager.Value('i', 1)
lock = manager.Lock()
proc = [Process(target=func1, args=(list1, value1, dict1, lock)) for i in range(20)]
for p in proc:
p.start()
for p in proc:
p.join()
print(list1)
print(dict1)
print(array1)
print(value1)
from multiprocessing import Process, Value, Lock
from multiprocessing.managers import BaseManager
class Employee(object):
def __init__(self, name, salary):
self.name = name
self.salary = Value('i', salary)
def increase(self):
self.salary.value += 100
def getPay(self):
return self.name + ': ' + str(self.salary.value)
class MyManager(BaseManager):
pass
def Manager2():
m = MyManager()
m.start()
return m
MyManager.register('Employee', Employee)
def func1(em, lock):
with lock:
em.increase()
if __name__ == '__main__':
manager = Manager2()
em = manager.Employee('zhangsan', 1000)
lock = Lock()
proces = [Process(target=func1, args=(em, lock)) for i in range(10)]
for p in proces:
p.start()
for p in proces:
p.join()
print(em.getPay())
进程池:当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法
Multiprocessing.Pool: 进程池,进程池能够管理一定的进程,当有空闲进程时,则利用空闲进程完成任务,直到所有任务完成为止。
Pool管理
Pool的执行流程,有三个阶段:
multiprocessing.Pool常用函数解析:
# coding: utf-8
import multiprocessing
import time
def func(msg):
print("msg:", msg)
time.sleep(3)
print("end")
if __name__ == "__main__":
pool = multiprocessing.Pool(processes=3)
for i in range(4):
msg = "hello %d" % (i)
pool.apply_async(func, (msg,)) # 维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
print("Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~")
pool.close()
pool.join() # 调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
print("Sub-process(es) done.")
Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。看上述代码的执行结果就知道这一段话的意思了。
https://blog.csdn.net/lechunluo3/article/details/79005910
https://www.cnblogs.com/kaituorensheng/p/4465768.html#_label0