python多进程主要用于解决python自身含有的GIL(即全局解释器锁)所导致的不能并行任务的问题,之前已经介绍了multiprocessing包的基本使用方式,本文简要介绍一下multiprocessing包中含有的几个模块pipe(管道)、queue(队列)、manager,这几个模块在某些较为复杂的实际应用中还是很有用处的。
queue(队列)
python自身本来就带有queue模块,此模块正如其名,发挥的是队列功能,当然这种队列的传递也是能够发挥通信作用的。python官方文档的解释是queue模块实现多生产者,多消费者队列,能够在多个生产者和消费者之间通信。当信息必须安全的在多线程之间交换时,它在线程编程中是特别有用的。此模块中的 Queue 类实现了所有锁定需求的语义。
简单说queue其实就像进程或线程锁一样,起到对信息的保护作用。由于队列的存在,python函数中的生产者函数及消费者函数能够准确、有确定方向的进行通信。使这些需要被传递的信息不会因为多线程或多进程的存在而出错。queue储存某些生产者函数产生的信息,再将这些信息一起传递给消费者函数。当然这些函数可以是在不同的进程中运行的,从而达到进程间通信的效果。使用了队列后就不会有信息提前或者滞后到达消费者进程处,这样也就不容易导致信息出错或者紊乱。
multiprocessing中queue模块包含的方法有qsize()(返回队列包含元素的多少)、empty()(即给出队列是否为空的判断,空为True)、full()(即给出队列是否已满的判断,因为开始的时候设置队列可以设置最大拥有元素的多少)、put()(即将元素放入队列)、get()(从队列中获取元素,注意每获取一个队列中元素就减少一个)、close()(表示当前队列不再放入新的元素)。
例:
import multiprocessing
def test(q):
for i in range(10):
q.put(i) # 将元素放入queue
q.close()
def get_q(q):
while not q.empty(): # 判断队列是否为非空,非空才继续执行下一步
res = q.get() # 获取一个元素
print(res)
# print(q.qsize())
if __name__ == '__main__':
q = multiprocessing.Queue() # 定义queue,这里可以传入参数,即队列所含最大元素量
p1 = multiprocessing.Process(target=test,args=(q,)) # 多进程
p2 = multiprocessing.Process(target=get_q,args=(q,))
# 此例仅为介绍用,实际应用时创建的进程可以有丰富的功能,只需要将queue作为一个参数传入进程即可。
p1.start()
p2.start()
p1.join()
p2.join()
结果:
pipe(管道)
pipe(管道)在编程语言中也是用于通信功能,即将某函数产生的结果通过管道的一端传递给管道的另一端,这就是管道名称的来源。python中管道一般用于多进程操作,可以用于在进程间传递消息。
与队列不同的地方是pipe能进行双向通信,但是队列只能单向传递消息。
例:
import multiprocessing
def test1(conn):
conn.send('test1传送到:jack') # 传递一个信息‘jack’
conn.send('test1传送到:mary') # 传递另个信息‘mary’
print(conn.recv()) # 获取从管道的另一端发送来的第一个值
print(conn.recv()) # 获取从管道的另一端发送来的第二个值
def test2(conn):
conn.send('test2传送:你好') # 传递一个信息‘你好’
conn.send('test2传送:HELLO') # 传递另个信息‘HELLO’
print(conn.recv()) # 获取从管道的另一端发送来的第一个值
print(conn.recv()) # 获取从管道的另一端发送来的第二个值
if __name__ == '__main__':
p_conn,c_conn = multiprocessing.Pipe() # 定义一个管道的两端,之后将这两端传出去
p1 = multiprocessing.Process(target=test1,args=(p_conn,)) # 把一端给test1
p2 = multiprocessing.Process(target=test2,args=(c_conn,)) # 另一端给test2
p1.start()
p2.start()
p1.join()
p2.join()
# 这样就将test1与test2函数进行了管道传输,test1的内容传给了test2,test2的内容传给了test1。
# test1与test2被分配到了不同的进程,因此这样相当于就进行了多进程中进程间的通信。
结果:
manager(共享内存管理)
manager模块用于多进程中共享内存的管理,其实也就是全局变量的管理。默认情况下子进程产生的全局变量仅子进程自身可以使用,主进程是获取不到子进程中全局变量的变化的。这也是因为当下使用的多核电脑的每个核都是分开工作的,即每个子进程都在不同核上工作,因此要设置全局变量就不是默认的,需要额外设置(注意:这一点不同于多线程,多线程中子线程的全局变量主线程能识别到,因为是单核运行)。
除了manager外,multiprocessing包还含有value、array也可以用于共享内存的处理。
使用value、array的例子:
import multiprocessing
def test(v,a):
v.value = 123 # 这里修改传入的value的值
for i in range(len(a)): # 这里逐个修改传入的array中的值
a[i] = 'o'
if __name__ == '__main__':
v = multiprocessing.Value('d') # 将v指定为Value
a = multiprocessing.Array('u','nihao') # 将a指定为字符型array,官方文档提示说后续u可能被删除
process = multiprocessing.Process(target=test,args=(v,a)) # 传入test函数,开启子进程
process.start()
process.join()
print(v.value) # 打印出v的值,检查是否被改变
print(a[:]) # 打印出a的序列,检查是否改变
结果:
python官方文档说明manager支持类型有: list 、 dict 、 Namespace 、 Lock 、 RLock 、 Semaphore 、 BoundedSemaphore 、 Condition 、 Event 、 Barrier 、 Queue 、 Value 和 Array 。
此处以list和dict为例:
import multiprocessing
def test(list1,dict1):
dict1['name1'] = 'jack' # 增加dict1的内容
dict1['name2'] = 'mary'
list1.reverse() # 将list1中的内容颠倒
if __name__ == '__main__':
with multiprocessing.Manager() as manager: # 引入manager
list1 = manager.list(range(5)) # 设置list1和dict1为共享对象
dict1 = manager.dict()
p = multiprocessing.Process(target=test,args=(list1,dict1)) # 传入函数中对list1与dict1修改
p.start()
p.join()
print(list1) # 检查是否修改
print(dict1)
结果:
叮!
其实除了上面介绍的这些模块外还有Lock()模块也是很重要的一个模块,但是使用起来是较为简单的,这里就不再介绍了。另外还看到了semaphore模块,此模块称为信号量,用于控制线程数,即只有线程获取了信号后才能运行否则就阻塞。semaphore模块是为了防止线程数太多导致任务出错或机器故障而设计的。
参考:https://docs.python.org/zh-cn/3.6/library/multiprocessing.html#multiprocessing.Queue.empty
参考:https://blog.csdn.net/haeasringnar/article/details/79917057