Python多进程multiprocessing

说到多进程首先想到的就是多进程间的通信方式:

  1. 管道(PIPE)
  2. 信号(Signal)
  3. 消息队列(Message Queue)
  4. 共享内存(Shared Memory)
  5. 信号量(Semaphore)
  6. 套接字(Socket)

然后就是C++多进程的实现:fork()函数,fork()很特殊有两个返回值,一个是子进程,另一个0或者子进程ID,python的os包中也带有fork函数也是类似的用法,但是问题就在与fork分叉的方法不能用于windows并且使用起来并不顺手,所以封装了多进程各种操作的包multiprocessing就变的非常重要。

multiprocessing官方文档:
https://docs.python.org/3/library/multiprocessing.html

相较于C/C++,Python的多线程因为历史原因,GIL锁问题会让Python多线程(threading)不能有效的利用多核心CPU,而C/C++线程对多核的利用效率可以达到100%,所以在Python中想提高线程对多核CPU的利用率要用C/C++来扩展,还好Python对于C扩展还是很容易,但是也有更好的解决方案那就是多进程实现并发。

1.Process

官方例子:

from multiprocessing import Process

def f(name):
    print('hello', name)

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

其中join是为进程添加阻塞,能保证p进程执行完成后,主进程才会退出。

更复杂一点:

from multiprocessing import Process
import os

def info(title):
    print(title)
    print('module name:', __name__)
    print('parent process:', os.getppid())
    print('process id:', os.getpid())

def f(name):
    info('function f')
    print('hello', name)

if __name__ == '__main__':
    info('main line')
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

这段代码不但会打印出hello bob还会打印出子进程的pid和父进程的pid

2.进程池Pool

看作是进程对象的容器,可以对进程进行分配和管理
利用Pool创建多个进程apply和apply_async
apply(func [,args [,kwds ] ] )回调func带参数args和关键字kwds,会阻塞,所以一般使用
apply_async(func [,args [,kwds [,callback [,error_callback ] ] ] ] )回调func函数带参数args和关键字kwds,非阻塞适合并行操作。

from multiprocessing import Pool
import os, time, random

def long_time_task(name):
    print('Run task %s (%s)...' % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print('Task %s runs %0.2f seconds.' % (name, (end - start)))

if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    p = Pool(4)
    for i in range(5):
        p.apply_async(long_time_task, args=(i,))
    print('Waiting for all subprocesses done...')
    p.close()
    p.join()
    print('All subprocesses done.')

官方给出的例子:

from multiprocessing import Pool
import time

def f(x):
    return x*x

if __name__ == '__main__':
    with Pool(processes=4) as pool:         # start 4 worker processes
        result = pool.apply_async(f, (10,)) # evaluate "f(10)" asynchronously in a single process
        print(result.get(timeout=1))        # prints "100" unless your computer is *very* slow

        print(pool.map(f, range(10)))       # prints "[0, 1, 4,..., 81]"

        it = pool.imap(f, range(10))
        print(next(it))                     # prints "0"
        print(next(it))                     # prints "1"
        print(it.next(timeout=1))           # prints "4" unless your computer is *very* slow

        result = pool.apply_async(time.sleep, (10,))
        print(result.get(timeout=1))        # raises multiprocessing.TimeoutError

其中的map函数就是内建函数的一种实现方式,是阻塞的(不阻塞的是map_async),但是在pool.map中不能使用匿名函数。imap返回的是一个迭代器。

3.进程间通信

队列Queue

from multiprocessing import Process, Queue

def f(q):
    q.put([42, None, 'hello'])

if __name__ == '__main__':
    q = Queue()
    p = Process(target=f, args=(q,))
    p.start()
    print(q.get())    # prints "[42, None, 'hello']"
    p.join()

简单的消息队列

from multiprocessing import Queue
from multiprocessing import Process
import random
import time
import os
def foo(name,task):
    if not task.empty():
        temp=task.get()
        print('%s pid:%s get %s parentsid:%d'%(name,os.getpid(),temp,os.getppid()))
    else:
        print('%s find the Queue is empty'%name)


if __name__=='__main__':
    resource=['a','b','c','d','e','f','g','h','i','j','k','l','m','n']
    x=random.sample(resource,4)
    task = Queue()
    for i in x:
        task.put(i)
    for i in range(5):
        Process(target=foo,args=('process%d'%(i+1),task)).start()
    time.sleep(1)

运行结果

process2 pid:8964 get f parentsid:9128
process3 pid:7560 get h parentsid:9128
process4 pid:8340 get d parentsid:9128
process1 pid:7564 get a parentsid:9128
process5 find the Queue is empty

管道(PIPE)
官方例子

from multiprocessing import Process, Pipe

def f(conn):
    conn.send([42, None, 'hello'])
    conn.close()

if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    p = Process(target=f, args=(child_conn,))
    p.start()
    print(parent_conn.recv())   # prints "[42, None, 'hello']"
    p.join()

pipe()创建的是一个双工的管道连接对象。这个例子中将 child_conn 传递给子进程,子进程发送信息,主进程用parent_conn连接对象接受子进程发送的消息。
请注意:如果两个进程(或线程)试图同时读取或写入管道的同一端,则管道中的数据可能会损坏。当然,同时使用不同管道的流程也不会有数据损坏风险。

锁机制(LOCK)
与线程中的锁机制差不多,利用锁机制可以使多个进程同步,解决对资源的争抢冲突。

官方例子:

from multiprocessing import Process, Lock

def f(l, i):
    l.acquire()
    try:
        print('hello world', i)
    finally:
        l.release()

if __name__ == '__main__':
    lock = Lock()

    for num in range(10):
        Process(target=f, args=(lock, num)).start()

同步常见防止管道冲突:

from multiprocessing import Process
from multiprocessing import Pipe
from multiprocessing import Lock

def foo(name,conn,lock):
    lock.acquire()
    try:
        conn.send('%s send hello'%name)
    finally:#就算执行不成功也释放 否则进程会被锁死
        lock.release()

if __name__=='__main__':
    pconn,conn=Pipe()
    kt=Lock()
    for i in range(5):
        Process(target=foo,args=('process%d'%(i+1),conn,kt)).start()
    for i in range(5):
        print(pconn.recv())
    print('process over!')

共享内存(Shared memory)

使用Arry和Value,官方的例子如下:

from multiprocessing import Process, Value, Array

def f(n, a):
    n.value = 3.1415927
    for i in range(len(a)):
        a[i] = -a[i]

if __name__ == '__main__':
    num = Value('d', 0.0)
    arr = Array('i', range(10))

    p = Process(target=f, args=(num, arr))
    p.start()
    p.join()

    print(num.value)
    print(arr[:])

输出结果:

3.1415927
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]

Value(name,value)将value绑定到name Array也是这样。

多进程还有一个好处就是因为,服务器和客户端之间,服务器与服务器之间,客户端与客户端的通信也是进程间通信,通过Manger可以实现分布式的进程,将任务分配给多个服务器进行处理,在廖雪峰的教程中也讲到了BaseManger,这个很有用,会在后面进行总结。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值