python之路——多进程(进程间通信、进程池)
进程
一个进程,包括了代码、数据和分配给进程的资源(内存),在计算机系统里直观地说一个进程就是一个PID。操作系统保护进程空间不受外部进程干扰,即一个进程不能访问到另一个进程的内存。有时候进程间需要进行通信,这时可以使用操作系统提供进程间通信机制。通常情况下,执行一个可执行文件操作系统会为其创建一个进程以供它运行。但如果该执行文件是基于多进程设计的话,操作系统会在最初的进程上创建出多个进程出来,这些进程间执行的代码是一样,但执行结果可能是一样的,也可能是不一样的。
为什么需要多进程?最直观的想法是,如果操作系统支持多核的话,那么一个执行文件可以在不同的核心上跑;即使是非多核的,在一个进程在等待I/O操作时另一个进程也可以在CPU上跑,提高CPU利用率、程序的效率。
在Linux系统上可以通过fork()来在父进程中创建出子进程。一个进程调用fork()后,系统会先给新进程分配资源,例如存储数据和代码空间。然后把原来进程的所有值、状态都复制到新的进程里,只有少数的值与原来的进程不同,以区分不同的进程。fork()函数会返回两次,一次给父进程(返回子进程的pid或者fork失败信息),一次给子进程(返回0)。至此,两个进程分道扬镳,各自运行在系统里。
python调用多进程模块为------->>>multiprocessing
import multiprocessing,os,time def func(name): print("%s is talking.....[father: %s ;own: %s]"%(name,os.getppid(),os.getpid())) # time.sleep(1) def f(name): func(name) if __name__ == "__main__": func("main") #父进程调用func for i in range(5): P = multiprocessing.Process(target=f,args=(i,)) #子进程调用f P.start()
其中:os.getppid() ------->> 输出父进程pid ; os.getpid() ------->> 输出当前子进程pid
进程间通讯
不同进程间内存是不共享的,要想实现两个进程间的数据交换,可以用以下方法:
队列(Queues)-----------> 与线程queues方法类似,但不同
import multiprocessing,time,os,queue def func(q): q.put(["japhi",1,"alex"]) if __name__ == "__main__": q = multiprocessing.Queue() #Queue 能使进程之间进行数据互通 # q = queue.Queue() 这个方法仅限线程,进程不能用 q.put("alex") p = multiprocessing.Process(target=func,args=(q,)) #实质上是将主进程的q复制了一份给子进程p p.start() print(q.get()) print(q.get())
将父进程的队列q传给子进程,实质上是对主进程的q进行了一个复制操作。
管道(pipe)
实例化管道后会产生两个值,一个给父进程,一个给子进程,就可以使数据通过管道在父进程和子进程中交互;类似于scoket通信。
import multiprocessing def func(con): print(con.recv()) con.send("收到主进程信息") #子进程收父进程发送的数据 con.close() if __name__ == "__main__": conn_a,conn_b = multiprocessing.Pipe() #实例化管道对象,主进程和子进程间可以进行数据通信 p = multiprocessing.Process(target=func, args=(conn_b,)) #将conn_b给了子进程 p.start() conn_a.send("您好,子进程") #父进程发送数据 print(conn_a.recv())
Manager
Manager()返回的manager对象控制一个包含Python对象的服务器进程,并允许其他进程使用代理来操作它们。
Manager()返回的manager将支持类型list,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Barrier,Queue,Value and Array。 例如,
import multiprocessing,os def mod(d,l): d[os.getpid()] =os.getppid() l.append(os.getpid()) print("\033[1;32m%s\033[0m"%l) print("\033[1;35m%s\033[0m"%d) if __name__ == "__main__": M = multiprocessing.Manager() # with multiprocessing.Manager() as M: 与上面的M = multiprocessing.Manager() 作用一致 dic = M.dict() #生成一个字典,可在多个进程间共享和传递 list1 = M.list(range(2)) #生成一个列表,可在多个进程间共享和传递 p_list = [] for i in range(5): p = multiprocessing.Process(target=mod,args=(dic,list1)) p.start() p_list.append(p) for res in p_list: res.join() #等待结果,不然程序会报错 print(dic) print(list1)
进程同步
what?
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()
进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
- apply 串行,且没有回调函数
- apply_async 并行
回调函数:指进程里的函数执行完会自动执行回调函数
import multiprocessing,os,time def f(name): time.sleep(1) print("%s from %s"%(name,os.getpid())) def b(a): print("------>>回调") if __name__ == '__main__': # po = multiprocessing.Pool(processes=3) #进程池中只能同时放入5个进程,可以直接写5 po = multiprocessing.Pool(3) #与上面的效果一样 for i in range(20): # po.apply(func = f,args=(i,)) ##串行 po.apply_async(func=f, args=(i,), callback=b) #并行,callback是回调,前面f函数执行完执行b print("Done") po.close() po.join() #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。