多进程编程
多进程
python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。Python提供了非常好用的多进程包multiprocessing,只需要定义一个函数,Python会完成其他所有事情。借助这个包,可以轻松完成从单进程到并发执行的转换。multiprocessing支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。
multiprocessing是一个包,它支持使用类似于线程模块的API来生成进程。multiprocessing包提供本地和远程并发,通过使用子进程而不是线程有效地旁路全局解释器锁。因此,multiprocessing模块允许编程人员充分利用给定机器上的多个处理器。它可以在Unix和Windows上运行。
为什么python下想要充分利用多核CPU,就用多进程?
因为每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行,所以在python中,多进程的执行效率优于多线程(仅仅针对多核CPU而言)
1.创建一个多进程
import multiprocessing import time def hello(name): print("Hello %s"%name) time.sleep(1) if __name__=='__main__': for i in range(10): i=multiprocessing.Process(target=hello,args=('P1',)) i.start()
import multiprocessing,threading import time def thread_run(num): print("I'm a thread of %sProcess"%num,threading.get_ident())#threading.get_ident()获取线程号 def hello(name): print("Hello %sProcess"%name) t1=threading.Thread(target=thread_run,args=(name,)) #在进程中插入线程 t1.start() time.sleep(1) if __name__=='__main__': for i in range(10): i=multiprocessing.Process(target=hello,args=('%s'%i,)) i.start()
import multiprocessing,threading import time import os def thread_run(num): print("I'm a thread of %sProcess"%num,threading.get_ident())#threading.get_ident()获取线程号 def hello(name): print("parent process:",os.getppid())#获取父进程号 每一个进程都是由父进程启动的,这里由pychram启动的 print("Hello %sProcess"%name,os.getpid())#os.getpid()获取进程号 t1=threading.Thread(target=thread_run,args=(name,)) #在进程中插入线程 t1.start() time.sleep(1) if __name__=='__main__': for i in range(10): i=multiprocessing.Process(target=hello,args=('%s'%i,)) i.start() # i.join()
进程间的通信
不同进程间内存是不共享的,要想实现两个进程间的数据交换,可以用以下方法:
Queues
# 使用方法跟threading里的queue差不多 from multiprocessing import Process, Queue def f(q): q.put(['li','male' ,23 ]) if __name__ == '__main__': q = Queue() p = Process(target=f, args=(q,)) p.start() print(q.get()) p.join() # 执行结果 # ['li', 'male', 23]
Pipes
Pipe()返回的两个连接对象表示管道的两端。每个连接对象都有send()和recv()方法(等等)。请注意,如果两个进程(或线程)尝试同时读取或写入管道的同一端,则管道中的数据可能会损坏。
Pipe()函数返回通过管道连接的一对连接对象,默认情况下是双向(双向)。例如:
from multiprocessing import Process, Pipe def f(conn): conn.send(['li','male' ,23 ]) conn.close() if __name__ == '__main__': parent_conn, child_conn = Pipe() p = Process(target=f, args=(child_conn,)) p.start() print(parent_conn.recv()) p.join()
注意:Queue和pipe的区别: pipe用来在两个进程间通信。Queue用来在多个进程间实现通信。而且Pipe和Queue,也只可接收可pickle对象,这些都是由它们的实现方式造成的。因为它们不支持不可pickle对象,带有系统状态的对象,如套接字可能不适用
Value + Array
Value + Array 是python中共享内存 映射文件的方法,利用Value或Array把数据存储在一个共享的列表中。速度比较快。
from multiprocessing import Process, Value, Array def f(n, a): n.value = n.value + 1 for i in range(len(a)): a[i] = a[i] * 10 if __name__ == '__main__': num = Value('i', 1) arr = Array('i', range(10)) p = Process(target=f, args=(num, arr)) p.start() p.join() print(num.value) print(arr[:]) p2 = Process(target=f, args=(num, arr)) p2.start() p2.join() print(num.value) print(arr[:]) #执行结果: 2 [0, 10, 20, 30, 40, 50, 60, 70, 80, 90] 3 [0, 100, 200, 300, 400, 500, 600, 700, 800, 900]
Manager
Python实现多进程间通信的方式有很多种,例如队列,管道等,但这些方式只适用于多个进程都是源于同一个父进程的情况。如果多个进程不是源于同一个父进程,只能用共享,信号量等方式,但是这些方式对于复杂的数据结构,例如Queue,dict,list等,使用起来比较麻烦,不够灵活。
Manager是一种较为高级的多进程通信方式,它能支持Python支持的的任何数据结构。
它的原理是:先启动一个ManagerServer进程,这个进程是阻塞的,它监听一个socket,然后其他进程(ManagerClient)通过socket来连接到ManagerServer,实现通信。
Manager()返回的管理器对象控制一个保存Python对象的服务器进程,并允许其他进程使用代理来操作它们。 Manager()返回的管理器将支持类型:list,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Barrier,Queue,Value和Array。例如,
import os from multiprocessing import Process, Manager def f(d,l): d[1] = '1' d['2'] = 2 d[0.25] = None l.append(os.getpid()) print(l) if __name__ == '__main__': with Manager() as manager:#等同于manager=Manager() d = manager.dict()#生成一个可在多个进程之间进行传递和共享的字典 d['1'] = '1' d['2'] = '2' d['3'] = None l = manager.list(range(5)) p_list = [] for i in range(10): p = Process(target=f, args=(d,l)) p.start() p_list.append(p) for res in p_list: res.join() print(d) print(l)
进程池
在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,10几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,这时候进程池Pool发挥作用的时候就到了。
Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。
multiprocessing 模块下的Pool类下的几个方法:
apply()
函数原型:
apply(func[, args=()[, kwds={}]])
该函数用于传递不定参数,主进程会被阻塞直到函数执行结束(不建议使用,并且3.x以后不在出现)。#实际上就是串行
apply_async()
函数原型:
apply_async(func[, args=()[, kwds={}[, callback=None]]])
与apply用法一样,但它是非阻塞且支持结果返回进行回调。#并行
#这里callback为回调函数,是通过父线程调用的,并非是子线程,那有什么作用的?很简单,举个例子,假如该线程需要调用函数连接数据库的,父进程只需连一次,常连接就可以写了,但如果子进程调用的话,假如有一百个线程的话,我们就要起一百个连接了,呜呜,性能就在这无形间降低了。
map()
函数原型:
map(func, iterable[, chunksize=None])
Pool类中的map方法,与内置的map函数用法行为基本一致,它会使进程阻塞直到返回结果。
注意,虽然第二个参数是一个迭代器,但在实际使用中,必须在整个队列都就绪后,程序才会运行子进程。
close()
关闭进程池(pool),使其不在接受新的任务。
terminate()
结束工作进程,不在处理未处理的任务。
注意:close()跟terminate()的区别在于close()会等待池中的worker进程执行结束再关闭pool,而terminate()则是直接关闭
join()
主进程阻塞等待子进程的退出,join方法必须在close或terminate之后使用。
创建一个进程池
from multiprocessing import Process, Pool import time def Foo(i): time.sleep(2) print('hello') return i + 100 def Bar(arg): print('-->exec done:', arg) if __name__=='__main__': pool = Pool(5)#允许进程池里同时放入进程池 同时执行的只有5个,其他的处于挂起状态 for i in range(10): # pool.apply_async(func=Foo, args=(i,), callback=Bar) #并行 callback回调(是通过主进程调用的) ,执行完Foo后再执行Bar pool.apply_async(func=Foo, args=(i,)) # pool.apply(func=Foo, args=(i,))#串行 print('end') pool.close() pool.join() # 进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。