进程的并行multiprocess!!!
multiprocessing
是一个用与threading
模块相似API的支持产生进程的包。multiprocessing
包同时提供本地和远程并发,使用子进程代替线程,有效避免 Global Interpreter Lock 带来的影响(ProcessPoolExecutor
其实也是一样的原理)。因此,multiprocessing
模块允许程序员充分利用机器上的多个核心。Unix 和 Windows 上都可以运行。
利用池子
我们先看看之前用线程怎么写一个并发:(基于Executor抽象类)
from concurrent.futures import ThreadPoolExecutor
def f(x):
return(x*x)
if __name__ == '__main__':
with ThreadPoolExecutor(max_workers=4,thread_name_prefix="test_") as pool:
a=[1,2,3,4]
future_result=pool.map(f,a)
for i in future_result:
print(i)
pool.shutdown(wait=True)
输出为1,4,9,16
再看看使用进程池(没有基于Executor抽象类,直接用纯粹的进程):
from multiprocessing import Pool
def f(x):
return x*x
if __name__ == '__main__':
with Pool(5) as p:
print(p.map(f, [1, 2, 3]))
直接创建进程对象
在 multiprocessing
中,通过创建一个 Process
对象然后调用它的 start()
方法来生成进程。
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()
在进程之间通信
multiprocessing
支持进程之间的两种通信通道:
队列
Queue
类是一个近似queue.Queue
的克隆。 例如:from multiprocessing import Process, Queue def f(q): q.put([1, None, 'hello']) def f_2(q): q.put([2, None, 'hello']) if __name__ == '__main__': q = Queue() p = Process(target=f, args=(q,)) p2 = Process(target=f_2, args=(q,)) p.start() p2.start() print(q.get()) # prints "[42, None, 'hello']" print(q.get()) p.join() p2.join() 输出: [2, None, 'hello'] [1, None, 'hello']
队列是线程和进程安全的。
管道
Pipe()
函数返回一个由管道连接的连接对象,默认情况下是双工(双向)。例如:from multiprocessing import Process, Pipe import time def f(conn): conn.send([1, None, 'hello']) conn.send([2, None, 'hello']) conn.send([3, None, 'hello']) time.sleep(2) conn.send([4, None, 'hello']) conn.close() if __name__ == '__main__': parent_conn, child_conn = Pipe() p = Process(target=f, args=(child_conn,)) p.start() while True: try: data=parent_conn.recv() print(data) except: print('no') p.join()
返回的两个连接对象
Pipe()
表示管道的两端。每个连接对象都有send()
和recv()
方法(相互之间的)。请注意,如果两个进程(或线程)同时尝试读取或写入管道的 同一 端,则管道中的数据可能会损坏。当然,同时使用管道的不同端的进程不存在损坏的风险。
管道原理:
上面的代码中,我做了个小实验,最终搞明白了管道的特点:一旦建成,除非主动销毁,在管道没有东西的时候,Pipe.rec()
是读不出来东西的,但是此时管道并没有被关闭,联想起做过的项目,也都是我把发端的进程强行停掉了,这个管道才会销毁。