Python学习第51天(进程池)
今天开始了进程池的内容,说的直白点就是去取一个合适的中间值,我们再编写代码的时候为了加快处理速度我们可以使用多进程,因为多进程属于并行,肯定会使运行速度更快,但是创建进程就需要独立的内存空间,这又是一项非常耗费资源的事情,为了保证运行速度,又可以使进程不多不少,所以才引入了进程池的概念。
先说一下今天的第一个知识,在我学习线程的时候会出一种因为线程切换导致运行出现乱码的情况。
import threading,time def num(): global a tmp = a time.sleep(0.1) a = a - 1 print(a) if __name__ == '__main__': p = [] a = 100 for i in range(100): t = threading.Thread(target = num ) t.start() p.append(t) for t in p: t.join() print(a)
这里因为在计算的过程中time.sleep(0.1),程序在运行过程中进行了线程的切换,导致初始值全部编程100,或者部分编程,最终没能实现递减的过程。
那么在多进程的过程中是否也存在类似的情况呢?
首先分析一下,进程之间是不共享数据的,他们即使进行了通信,也都是复制过去的,各自肚子编写自己的程序,不存在共有的情况,
但是依旧存在着一种比较特殊的情况,比如多进程会同时共享我们的屏幕输出资源,当同时多个进程输出print语句的时候,就可能出现乱码的情况
from multiprocessing import Process def foo(i): print('hello world %s'%i) if __name__ == '__main__': for i in range(10): Process(target = foo , args = (i,)).start()
此时多个进程同时开始运行,并且输出hello world num ,也就是说在同一时间,屏幕显示会接收到多个指令,这就会导致屏幕在显示的过程中会出现乱码情况(请使用dos命令控制台演示)
所以为了解决这个类似于在多线程中发生的问题,我们就引入进程的锁,就是进程的同步。
from multiprocessing import Process, Lock def f(l, i): with l.acquire(): print('hello world %s'%i) if __name__ == '__main__': lock = Lock() for num in range(10): Process(target=f, args=(lock, num)).start()
大致就是上面的这个样子,这样完成操作之后,就不会出现乱码的现象了。
Without using the lock output from the different processes is liable to get all mixed up.
然后紧接着就是进程池的内容,进程池的大致概念就是A、B两地分开,在A点有100件货物,需要搬送到B处,每人每次能搬一个,利用之前进程的概念,要么是一个人来回100趟,要么是找100个人一次搞定,但是这个过程中就存在这么一个因素,找100个人太拥挤,1个人又太慢,所以我们就在中间加入一个进程池,限定同时运行的进程数,从而保证既不会太拥挤,同时也不会太慢。
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:
apply 同步情况
apply_async 异步情况
为了保证运行的速度,当然是采用异步,不用等待的那种,先看一个最简单的调用方式吧
from multiprocessing import Process,Pool import time,os def Foo(i): time.sleep(1) print(i) return i+100 pool = Pool(5) Bar(1) print("----------------") for i in range(10): pool.apply_async(func=Foo, args=(i,)) pool.close() pool.join() print('end')
以上就是他的大致使用方式,此时,上面会每秒输出5个数据来,关于pool = Pool()括号内的数字,当你不填写的时候就是默认你电脑的内核数(尽然可以用来测试你电脑的内核数)
其实pool.apply_async一共有三个参数,分别是func = Foo,args以及callback
callback:回调函数,当一个动作执行成功或执行完毕后会,该回调函数就会执行,而放在这里的回调函数有什么用呢,先看一下回调函数的情况吧
from multiprocessing import Process,Pool import time,os def Foo(i): time.sleep(1) print(i) return i+100 def Bar(arg): print('hello world') pool = Pool(5) Bar(1) print("----------------") for i in range(10): pool.apply_async(func=Foo, args=(i,),callback=Bar) pool.close() pool.join() print('end')
此时每当函数Foo运行完了之后都会去调用Bar,但是这个时候就会好奇了,既然运行完才调用,我直接写在Foo的后面不就搞定了,下面就再看一个演示吧
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) Bar(1) print("----------------") for i in range(10): pool.apply_async(func=Foo, args=(i,),callback=Bar) pool.close() pool.join() print('end')
此时你会发现,返回的Bar函数所在的进程和主进程的进程号完全相同,也就是说Bar方法其实是属于主进程的运行函数,所以这么调用的意义就出来了
比如当我们需要对程序进行logger日志操作,没操作完一次都会进行一次动作记录,但是如果直接写在各自的子进程的函数中,就会发生多个日志记录的情况,但是我们通过回调函数callback,使我们的日志函数永远在主进程中没从而保证了日志的唯一性
这里有一个需要注意的地方:
pool.close()
pool.join()
这两句属于一个固定写法,没法讲道理的那种。
昨天早睡,上班精神状态异常的好,但是今天貌似又要睡晚了,最近学习逐步养成了一种状态,纪检知识也是想多学习了,其实中午就可以做这个,该着急知识的积累了,早点睡,今天51天,感觉也就是一瞬间。