Python攻克之路-创建进程的两种方式
1.多进程
由于GIL的存在,python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分需要使用进程.Python提供了非常好的多进程包multiprocessing,只需要定义一个函数,Python会完成其他所有事情.借助这个包,可以轻松完成从单进程到并发执行的转换.multiprocessing支持子进程,通信和共享数据,执行不同形式的同步,提供process,queue,pipe,lock等组件。
multiprocessing包是Python中的多进程管理包.与threading.Thread类似,它可以利用multiprocessin.Process对象来创建一个进程.该进程可以运行Python程序内部编写的函数.该Process对象与Thread对象的用法相同,也有start(),run(),join的方法,此外multiprocessing包中也有lock/Event/Semophore/Condition类(这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致.所以,multiprocessing的很大一部分与threading使用同一套API,只不过换到多进程的情境。
在使用这此共享API的时候,我们要注意以下几点:
- 在UNIX平台上,当某个进程终结之后,该进程需要被父进程调用wait,否则进程成为僵尸进程。所以,有必要对每个Process对象调用join()方法(实际上等同于wait).对于多线程来说,由于只有一个进程,所以不存在此必要性
- multiprocessing提供了threading包中没有的IPC(比如Pipe和Queue),效率上更高。应优先考虑Pipe和Queue,避免使用lock/event/semaphore/condition等同步(因为它们占据的不是用户进程的资源)
- 多进程应该避免共享资源。在多线程中,比较容易使用共享资源,比如使用全局变量或者传递参数。在多进程情况下,由于每个进程有自己独立的内存空间,以以方法并不合适。此时可以共享内存和mangaer的方法共享资源。但这样做提高了程序的复杂度,并因为同步需要而降低了程序的效率。
ProcessPID中保存有PID,如果进程还没有start(),即PID为None
在windows系统下,需要注意的是要想启动一个子进程,必须加上那句if__name__=="main",进程相关的要写在这句下面.
2.实例分析
[root@node2 multiprocess]# cat multi-process.py #!/usr/local/python3/bin/python3 from multiprocessing import Process #引用进程模块 import time def f(name): time.sleep(1) print('hello',name,time.ctime()) if __name__=='__main__': p_list=[] for i in range(3): p = Process(target=f, args=('reid',)) #创建一个进程对象,再传入一个函数f为参数 p_list.append(p) p.start() for p in p_list: p.join() print('end') [root@node2 multiprocess]# python3 multi-process.py #程序中有一个主进程,三个子进程,进程之间是独立运行的,互不干扰 hello reid Sun Jun 10 10:13:57 2018 hello reid Sun Jun 10 10:13:57 2018 hello reid Sun Jun 10 10:13:57 2018 end
类式调用
[root@node2 multiprocess]# cat muti-process-class.py #!/usr/local/python3/bin/python3 from multiprocessing import Process import time class MyProcess(Process): #继承multiprocessing下的Process def __init__(self): super(MyProcess,self).__init__() #继承父类的init方法 def run(self): time.sleep(1) print('hello',self.name,time.ctime()) #三个进程同时打印,self.name本身有值,进程对象下的一个属性,进程名,如进程1,进程2等,也可以赋值,在实例化对象时,传入一个参数 if __name__=='__main__': p_list=[] for i in range(3): p = MyProcess() #进程对象 p.start() #启动 p_list.append(p) for p in p_list: p.join print('end') [root@node2 multiprocess]# python3 muti-process-class.py end hello MyProcess-1 Sun Jun 10 11:43:24 2018 hello MyProcess-3 Sun Jun 10 11:43:24 2018 hello MyProcess-2 Sun Jun 10 11:43:24 2018
父进程与子进程间的关系
[root@node2 multiprocess]# cat par-chil.py #!/usr/local/python3/bin/python3 from multiprocessing import Process import os import time def info(title): print(title) #传输什么,打印什么 print('module name: ',__name__) #__name__是main print('parent prcess: ',os.getppid()) #打印父进程号 print('process id: ', os.getpid()) #打印进程号 def f(name): info('\033[31;1mfunction f\033[0m') print('hello',name) if __name__=='__main__': info('\033[32;1mmain process line \033[0m') #传入参数,执行info函数 time.sleep(3) p = Process(target=info,args=('bob',)) #创建子进程 p.start() p.join() [root@node2 multiprocess]# python3 par-chil.py main process line module name: __main__ 主进程 parent prcess: 13838 process id: 14521 ### bob module name: __main__ 子进程 parent prcess: 14521 ### process id: 14522
3.进程通信和数据共享
分析:线程可以共享数据,进程之间实现通信的方法是使用pipe和queue
(1)、Queue
[root@node2 multiprocess]# cat que.py #!/usr/local/python3/bin/python3 from multiprocessing import Process,Queue def f(q): q.put([42,2,'hello']) if __name__=='__main__': q = Queue() #进程队列 p_list=[] for i in range(3): p = Process(target=f,args=(q,)) #q作为参数由父进程传给子进程,因为正确情况下,父进程和子进程不能通信 p_list.append(p) p.start() print(q.get()) print(q.get()) print(q.get()) for i in p_list: i.join() [root@node2 multiprocess]# python3 que.py [42, 1, 'hello'] [42, 0, 'hello'] [42, 2, 'hello']
确认def f中的q和p = Process(target=f,args=(q,i))中的q
[root@node2 multiprocess]# cat que.py #!/usr/local/python3/bin/python3 from multiprocessing import Process,Queue def f(q): q.put([42,2,'hello']) print('subprocess q id: ', id(q)) ###id一样表示是共享的,不一样表示copy的 if __name__=='__main__': q = Queue() p_list=[] print('main q id: ', id(q)) ### for i in range(3): p = Process(target=f,args=(q,)) p_list.append(p) p.start() print(q.get()) print(q.get()) print(q.get()) for i in p_list: i.join() [root@node2 multiprocess]# python3 que.py main q id: 140394463540392 subprocess q id: 140394463540392 #windows测试可能不一样 [42, 2, 'hello'] subprocess q id: 140394463540392 subprocess q id: 140394463540392 [42, 2, 'hello'] [42, 2, 'hello']
(2)、Pipe
分析:父进程处理阻塞状态等待接收,子进程进行发送数据
[root@node2 multiprocess]# cat pipes.py #!/usr/local/python3/bin/python3 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,)) #把子进程的通道作为参数传入子进程,通过函数f可以进行send和reveice,而且在主进程也有parent conn p.start() #启动一个子进程 print(parent_conn.recv()) #父进程在等待接收(阻塞),直到子进程执行f函数时,有个子进程的管道进行conn.send发送数据,parent_conn可以接收 p.join() [root@node2 multiprocess]# python3 pipes.py [42, None, 'hello']
(3)、Manager
描述: 数据共享 [root@node2 multiprocess]# cat manager.py #!/usr/local/python3/bin/python3 from multiprocessing import Process, Manager def f(d,l,n): #d是字典,l是列表,n是i一个0-9的值 d[n] = '1' #d实际是manager创建的 d['2'] = 2 d[0.25] = None #字典d,当一个进程进入后,操作后有三个键值对,第二个进程进入后,d,l是一样的,会被覆盖掉,如果把n修改成1就变成三个键值对, #因为第一个进程进入时,字典是空的,会首先创建三个键值对,第二个进程进入时,会进行覆盖,因为字典是共用的,d[1] = 1都一样, #所以10个进程走完后,三个键值对不还是一样 l.append(n) #往列表中添加值 if __name__=='__main__': with Manager() as manager: ##manager = manager() d = manager.dict() #创建一个空字典实现进程之间共享 l = manager.list(range(5)) #创建一个从0到4的列表 p_list = [] for i in range(10): #创建10个进程 p = Process(target=f,args=(d,l,i)) p.start() p_list.append(p) for res in p_list: res.join() print(d) print(l) [root@node2 multiprocess]# python3 manager.py {0: '1', '2': 2, 0.25: None, 3: '1', 5: '1', 1: '1', 6: '1', 4: '1', 7: '1', 8: '1', 2: '1', 9: '1'} #三组键值对 [0, 1, 2, 3, 4, 0, 3, 5, 6, 1, 4, 7, 8, 2, 9] ##0,1,2,3,4是初始时就有
分析:主进程和子进程是否一致
[root@node2 multiprocess]# cat manager.py #!/usr/local/python3/bin/python3 from multiprocessing import Process, Manager def f(d,l,n): d[n] = '1' d['2'] = 2 d[0.25] = None l.append(n) print('sub',id(d)) ######### if __name__=='__main__': with Manager() as manager: d = manager.dict() l = manager.list(range(5)) p_list = [] print('main',id(d)) ####### for i in range(10): p = Process(target=f,args=(d,l,i)) p.start() p_list.append(p) for res in p_list: res.join() print(d) print(l) [root@node2 multiprocess]# python3 manager.py ##结果windows不一样,linux中是一样的 main 140423045960872 sub 140423045960872 sub 140423045960872 sub 140423045960872 sub 140423045960872 sub 140423045960872 sub 140423045960872 sub 140423045960872 sub 140423045960872 sub 140423045960872 sub 140423045960872 {0: '1', '2': 2, 0.25: None, 3: '1', 2: '1', 4: '1', 7: '1', 8: '1', 5: '1', 1: '1', 9: '1', 6: '1'} [0, 1, 2, 3, 4, 0, 3, 2, 4, 7, 8, 1, 5, 6, 9