Python多进程

目录

多进程访问冲突

多进程通信

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()

此时两个进程的输出内容会抢占屏幕:

解决方法:

  • 子进程join(),主进程等待子进程结束。即去注释掉上面的两行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实现了数据共享。

mp.Queue

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())  # 主进程取出数据

mp.Pipe

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()

Value\Array

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还可以共享类的实例对象。

Manager共享Value、Array、dict、list、Lock、Semaphore等

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)

mp.Manager共享自定义类的实例对象

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())

Python进程池

进程池:当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法

Multiprocessing.Pool: 进程池,进程池能够管理一定的进程,当有空闲进程时,则利用空闲进程完成任务,直到所有任务完成为止。

Pool管理

Pool的执行流程,有三个阶段:

  • 一个进程池接受很多任务,然后分开执行任务
  • 当进程池中没有进程可以使用,则任务排队
  • 所有进程执行完成,关闭连接池,完成进程

multiprocessing.Pool常用函数解析

  • apply_async(func[, args[, kwds]]) :使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程
  • close():关闭Pool,使其不再接受新的任务;
  • terminate():不管任务是否完成,立即终止;
  • join():主进程阻塞,等待子进程的退出, 必须在closeterminate之后使用;
# 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

  • 9
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值