说到多进程首先想到的就是多进程间的通信方式:
- 管道(PIPE)
- 信号(Signal)
- 消息队列(Message Queue)
- 共享内存(Shared Memory)
- 信号量(Semaphore)
- 套接字(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,这个很有用,会在后面进行总结。