Python3基础-进程同步与进程池
基本概念 多个进程可以协同工作来完成一项任务,通常需要共享数据。所以在多进程之间保持数据的一致性就很重要,需要共享数据协同的进程必须以适当的策略来读写数据。同步原语和线程的库类似。 - Lock:一个Lock对象有两个方法acquire和release来控制共享数据的读写权限。 - Event:一个进程发事件的信号,另一个进程等待事件的信号。Event对象有两个方法set和clear来管理自己内部的变量。 - Condition:此对象用来同步部分工作流程,在并行的进程中,有两个基本的方法,wait()用来等待进程,notify_all用来通知所有等待此条件的进程。 - Semaphore:用来共享资源,比如:支持固定数据的共享连接。 - RLock:递归锁对象,其用途和方法同Threading模块一样。 - Barrier:将程序分成几个阶段,适用于有些进程必须在某些特性进程之后执行,处于Barrier之后的代码不能同处于Barrier之前的代码并行。
跟线程的互斥锁(又叫全局锁也叫同步锁)作用几乎是一样的。
都是用来给公共资源上锁,进行数据保护的。
当一个进程想去操作一个公共资源,它就可以给公共资源进程“上锁”的操作,其他进程如果也想去访问或者操作这个公共资源,那么其他的进程只能阻塞,等待刚刚的进程把锁释放,下一个进程才可以对这个公共资源进行操作。
from multiprocessing import Process def f(i): print('hello world %s' % i) if __name__ == '__main__': for num in range(10): Process(target=f, args=(num,)).start()
from multiprocessing import Process, Lock def f(l, i): l.acquire() print('hello world %s' % i) #当执行到这句话的时候,就不再是并行了,和线程中锁的概念是一样的 l.release() if __name__ == '__main__': lock = Lock() for num in range(10): Process(target=f, args=(lock, num)).start()
输出结果:
hello world 1
hello world 0
hello world 2
hello world 3
hello world 4
hello world 5
hello world 6
hello world 7
hello world 8
hello world 9
-
进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:
- apply
- apply_async
from multiprocessing import Process,Pool import time,os def Foo(i): time.sleep(1) print(i) return i+100 def Bar(arg): print(os.getpid()) print(os.getppid()) print('logger:',arg) pool = Pool(5) #创建线程池对象,并指定最大并发量,如果不指定,会根据当前计算机的cpu核数来自动调整 Bar(1) print("----------------") for i in range(5): #创建10个进程 #pool.apply(func=Foo, args=(i,)) #pool.apply_async(func=Foo, args=(i,)) pool.apply_async(func=Foo, args=(i,),callback=Bar) #这里使用了callback回调函数 pool.close() #就是使用进程池,一定要先close再join,否则就会报错。先close后join这个顺序是固定的 pool.join() #没有join的话,进程池里的进程是不会运行的。 print('end')
运行报错:
RuntimeError: An attempt has been made to start a new process before the current process has finished its bootstrapping phase. This probably means that you are not using fork to start your child processes and you have forgotten to use the proper idiom in the main module: if __name__ == '__main__': freeze_support() ... The "freeze_support()" line can be omitted if the program is not going to be frozen to produce an executable.
原因:多进程需要在main函数中运行,
解决方法1:
加main函数,在main中调用
from multiprocessing import Process,Pool import time,os def Foo(i): time.sleep(1) print("i===:",i) return i+100 def Bar(arg): print("os.getpid==",os.getpid()) print("os.getppid=",os.getppid()) print('logger:',arg) print("*****************") if __name__ == '__main__': pool = Pool(5) #创建线程池对象,并指定最大并发量,如果不指定,会根据当前计算机的cpu核数来自动调整 Bar(1) print("----------------") for i in range(20): #创建10个进程 pool.apply_async(func=Foo, args=(i,),callback=Bar) #这里使用了callback回调函数 #pool.apply(func=Foo, args=(i,)) 同步接口
pool.close() #就是使用进程池,一定要先close再join,否则就会报错。先close后join这个顺序是固定的
pool.join() #没有join的话,进程池里的进程是不会运行的。
print('end')
下面开始对代码进行分析,并且对进程池中一些常用的方法进行讲解:
-
首先来说说,在进程池中添加进程的两种模式。
分别是apply(同步执行模式)和apply_async(异步执行模式)。
1.1 apply(同步执行模式)
这种模式一般情况下没人会去用,如果进程池使用了这种模式,当进程池的第一个进程执行完毕后,才会执行第二个进程,第二个进程执行完毕后,在执行第三个进程....(也就是说这种模式会阻塞主进程!),无法实现并行效果。(不止如此,这种模式还不支持callback回调函数。)
1.2 apply_async (异步执行模式)
异步的执行模式,才是可以实现并行效果的模式,支持callback回调函数,当一个进程没有执行完毕,没有返回结果,异步执行的模式并不会对主进程进行阻塞!
补充一点!虽然 apply_async是非阻塞的,但其返回结果的get方法却是阻塞的,如使用result.get()会阻塞主进程!
1.3 pro_pool.apply_async(func=func1,args=(i,),callback=display_pro_info)
语法和创建多线程多进程没有什么差别,func用于指定一个进程要运行的函数,args用来给函数传参数,callback用来指定回调函数。
2.关于join,close,terminate。
2.1 join 主进程阻塞等待子进程运行结束后退出, join方法要在close或terminate之后使用。(换种说法就是,不加join方法,进程池里的进程根本不会执行~)
2.2 close 关闭进程池,不再接受新的任务
2.3 terminate 直接结束进程,不再处理没有处理的任务
#其实join和close比较常用。
3.关于callback函数的一个简单说明。
在了解回调函数之前,需要注意!!callback函数是由主进程去调用的,并不是子进程!!从刚刚的示例中就可以看出结果!
某一个函数执行完毕后,某个函数或者动作执行成功后,再去执行的一个函数,并且之前执行成功的那个函数的返回值,会作为参数传给callback函数。
让回调函数在主进程下调用有什么好处?
比如现在需要开10个线程去对数据库中的数据进行操作,现在有个需求,就是对数据库内部进行的操作都需要记录一个日志,我们可以把写日志这个函数做为一个回调(callback)函数。
在不使用callback函数之前,如果想去开10个进程去操作数据库,这10个进程是并发执行的,每个进程都去操作数据库后,同时去写一个日志文件,如果不加锁的话很容易造成日志文件的损坏,所以我们可以把写日志的函数去交给主进程去执行,当进程池中的10个进程运行结束后,主线程直接执行一个写日志的操作,这就是我理解的callback函数的用处。
拿进程池中的callback来举例吧,pro_pool.apply_async(func=func1,args=(i,),callback=display_pro_info)当func1函数内部执行成功后(只要返回值不为假),display_pro_info这个函数就会被作为回调函数去执行(当然执行这个函数的是主进程!),func1的返回值会作为参数传给display_pro_info这个函数。
最后补充一下,使用callback回调函数需要注意的几点。
-
在执行回调函数之前的那个函数,必须有一个返回值!这个返回值有两个用途,第一个用途是判断这个函数是否执行成功,执行成功才可以执行callback函数,另一个用途就是这个返回值会作为参数传给回调函数。
-
callback回调函数本身必须接收一个参数,这个参数用来接收上一个函数执行完毕后的返回值。
浙公网安备 33010602011771号