1.进程、与线程区别
2.cpu运行原理
3.python GIL全局解释器锁
4.线程
1.语法
2.join
3.线程锁之Lock\Rlock\信号量
4.将线程变为守护进程
5.Event事件
6.queue队列
7.生产者消费者模型
8.Queue队列
9.开发一个线程池
5进程
1.语法
2.进程间通讯
3.进程池
进程与线程
什么是线程(thread)?
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
什么是进程(process)?
进程与线程的区别?
CPython中全局解释性锁是什么鬼?Python GIL(Global Interpreter Lock)
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行,擦。。。,那这还叫什么多线程呀?莫如此早的下结结论,听我现场讲。
首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL
4.线程
Python threading模块 (线程模块)
1.语法
线程有2种调用方式,
方式1:直接调用. (用这种的多,因为简单)
方式2:定义一个继承线程模块的类,然后在类中写一个run方法. (用的不多,但是要知道,以后编程不仅要看自己的代码还要看别人的代码)
方式1代码举例:
1 #/usr/bin/env python3.5 2 #-*-encoding:utf8-*- 3 #__authro__:'ted.zhou' 4 ''' 5 多线程之直接调用方式 6 ''' 7 8 import threading 9 import time 10 11 def sayhi(num): # 定义每一个线程要执行的函数 12 print('running thread number:%s'%num) 13 time.sleep(3) # 这里睡3秒钟,是为了让大家看清楚python解释器解释多线程时,这个来回切换而让我们觉得线程是并行的.因为2个或更多,等待最长3秒,不是累计等待 14 15 if __name__ == '__main__': 16 t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例 17 t2 = threading.Thread(target=sayhi,args=(2,)) #又生成了一个线程实例 18 19 t1.start() 20 t2.start() 21 22 print(t1.getName()) 23 print(t2.getName())
方式2代码举例:
#!/usr/bin/env python3.5 #__author__:'ted.zhou' import threading import time class mythread(threading.Thread): ''' 定义一个多线程类,类中必须包含run方法,run方法就是实例化此类start()方法内部调用的方法 ''' def __init__(self,num): ''' 定义构造方法,采用新式类继承therading.Thread类 :param num: :return: ''' threading.Thread.__init__(self) # 经典类继续方法 # super(mythread,self).__init__(self) # 新式类继承方法执行报错,不知道为什么 self.num=num def run(self): print('now run thread number:%s'%self.num) time.sleep(3) if __name__ == '__main__': t1 = mythread(1) t2 = mythread(2) t1.start() t2.start() print('主线程执行完成!')
2.join
我们知道一个进程中至少有一个线程,子线程都是由这个主线程触发的.当自线程一旦启动,它和主线程并行.
所以python解释器在解释子线程的同时也在解释主线程,所以不会像我们想象那样(主线程会挂起,等带所有的子线程都解释执行完成才继续执行.)
但是实际使用中我们又有需求,要求子线程执行完后在执行主线程.那么怎么实现的.这时就需要用到threading.join()方法.具体实现代码,我们用
多线程模块调用 方式1 举例:
1 #!/usr/bin/env python3.5 2 #__author__:"ted.zhou" 3 ''' 4 多线程的join方法举例 5 ''' 6 import threading 7 import time 8 9 def sayhi(num): 10 print('now running thread number:%s'%num) 11 time.sleep(3) 12 print('子线程%s执行结束'%num) 13 14 if __name__ == '__main__': 15 t1 = threading.Thread(target=sayhi,args=[1,]) 16 t2 = threading.Thread(target=sayhi,args=[2,]) 17 18 t1.start() 19 t2.start() 20 # t1.join() # 先注释join方法 21 # t2.join() # 先注释join方法 22 print('主程序执行完成') 23 24 执行结果 25 now running thread number:1 26 now running thread number:2 27 主程序执行完成 28 子线程2执行结束 29 子线程1执行结束 30 31 Process finished with exit code 0
我们看到上面是,主线程结束后,子线程还在跑.当我们把join方法注释去掉,执行结果如下:
1 now running thread number:1 2 now running thread number:2 3 子线程1执行结束 4 子线程2执行结束 5 主程序执行完成
我们看看到,主线程一直在线程1 和线程2执行完成后,才继续往下走.
这里我们看,有两个子进程 我们使用了t1.join()和t2.join(),我们会想是不是每一个进程都需要加一个join方法.其实不是的
join方法一旦在代码中出现,它只会阻塞主线程,直至调用join方法的子线程完成才能继续.而子线程不受join方法的影响,继续执行.
那么我们会说我们调用一个join方法不就行了吗.理论上线程2比线程1晚运行,我们这里只需要执行t2.join()方法,理论上t1要比t2线程先完成.但是python解释器内部切换不是按你启动顺序,而是随机的.
所以为了保险起见,在处理多线程时,我们应该为每一个线程调用join()方法.
假如启动了100个线程,代码如下:
1 #!/usr/bin/env python3.5 2 #__author__:"ted.zhou" 3 ''' 4 多线程的join方法举例 5 ''' 6 import threading 7 import time 8 9 def sayhi(num): 10 print('now running thread number:%s'%num) 11 time.sleep(3) 12 print('子线程%s执行结束'%num) 13 14 if __name__ == '__main__': 15 thread_list = [] 16 for i in range(100): 17 t = threading.Thread(target=sayhi,args=[i,]) # 实例化子线程 18 thread_list.append(t) # 将子线程实例加入列表 19 t.start() 20 for t in thread_list: # 对线程实例列表循环 21 t.join() # 每一个都调用join()方法 22 print('主程序执行完成')
3.线程锁之Lock\Rlock\信号量
首先我们要知道为什么要加线程锁?
Rlock是多线程锁的情况存在时,必须将lock换成Rlock,所以有同学说无论是不是多层锁,我们都用Rlock,这样就一劳永逸了.此说法成立.
紧接着我们会问那为什么lock锁还存在呢?用老师的说法(你学python为什么先学面向过程编程,再学面向对象过程?)这有一个由浅入深的概念.
信号量: 线程锁 lock\Rlock 实现了,被锁定的代码串行运行,那么不是所有情况都是1个1个来,有时候也可以3个一起运行.所以线程中出现了信号量概念,让多少个线程同时运行.
3.1 线程锁(互斥锁)
首先我们先了解为什么要加线程锁?我们先看一个例子:
#!/usr/bin/env python3.5 #__author__:'ted.zhou' ''' 多个线程访问一个全局变量时的例子 ''' import threading import time A = 10 def subtraction(threadId): global A print("线程%s得到的值:%s"%(threadId,A)) time.sleep(1) # 这里睡1秒钟的目的是为了让python解释器切换GIL全局解释性锁.不然这执行速度,会导致你没切换呢,线程1都已经执行完了. A -= 1 print("线程%s执行后变量A的结果%s"%(threadId,A)) if __name__ == '__main__': thread_list = [] for i in range(1,4): t = threading.Thread(target=subtraction,args=[i,]) thread_list.append(t) t.start() for t in thread_list: t.join() print("变量A最后获得的结果:%s"%A) 执行的结果: 线程1得到的值:10 线程2得到的值:10 线程3得到的值:10 线程2执行后变量A的结果9 线程1执行后变量A的结果8 线程3执行后变量A的结果7 变量A最后获得的结果:7
假如循环100次,得到的结果可能就不一样了.那么我们有两个疑问:
对多线程锁节的疑问:
1.为什么得到一样的A =10 ,3个线程做 A -=1操作后的结果会累加 最终打印A的值是7?
2.既然有了GIL全局解释性锁,为啥还要用 线程锁?
那么我们来分析执行过程:
a.创建3个线程,就是拷贝了3次A =10 的变量。因为多线程 共享进程的内存,所以我觉得这里不是拷贝,这里使用的就是进程里的A=10的全局变量。3个线程对这一个全局变量进行-1操作,结果不就是7。
b.我们知道python解释器在解释代码时,读入内存-》词法、语法分析 -》编程成字节码 -》机器码 -> cpu执行
所以上述代码首先都会编译成字节码。python解释器在处理多线程任务时,其实就是内部有一个切换机制,不停的在多个线程之间切换,被切换到的就把全局解释器锁加在这个线程上,并且调用C语言模块的线程接口对这个线程上的一段字节码转换成机器码,然后再执行。
所以我们在第一行代码print(A)时,打印的值都是一样。紧接着,当执行到A -= 1这句代码时,这句代码其实在经过python解释器编译成字节码时,它可就不是一句字节码了,可能有多字节码(这多句字节码可能实现了两个功能1.把 A这个变量做了减1操作 2.把减1后的结果返回给 A )。
python解释器在内部切换时,可能就在处理一个线程的-1操作时,就切换到其他线程上去了。从而导致,本来处理 -1 并赋值给变量A的操作,只做了一半,就切换到其他进程上了。
那么就会出现当全局解释器锁被切换到其他线程上时,其他线程用变量A(这时还是10)再去做-1操作,巧合的是,它做完-1操作后,这时切换的时间还没到,所以紧接着这个进程下又做了赋值操作。
那么当再次切换到前面那个未做赋值操作的线程时,它接着之前的字节码继续执行,就会再次对变量A再次赋值。那么问题来了,两次赋值一样,那得到的结果肯定就会冲掉一个。那么我们当然想 把 -1操作 和给变量A赋值 操作一次执行了。
那么怎么才能实现呢。 这时就该线程锁出场了, 在 A -=1 前加一个锁,在A -=1 后 释放锁,而python解释器一旦看到这个锁,解释器切换机制就明白了,下面不能随便切换了,要等看到这个线程在下面的代码中出现释放锁的标记才进行正常切换。从而保证了多线程对全局变量操作的结果不混乱。
上面的解释应该能明白为什么需要加线程锁了,知道了为什么要加,才能在实际编程使用自如.代码如下:
1 #!/usr/bin/env python3.5 2 #__author__:'ted.zhou' 3 ''' 4 多个线程访问一个全局变量时的例子 5 ''' 6 import threading 7 import time 8 9 10 11 def subtraction(threadId): 12 global A 13 print("线程%s得到的值:%s"%(threadId,A)) 14 time.sleep(1) # 这里睡1秒钟的目的是为了让python解释器切换GIL全局解释性锁.不然这执行速度,会导致你没切换呢,线程1都已经执行完了. 15 lock.acquire() #修改数据前加锁 16 A -= 1 17 print("线程%s执行后变量A的结果%s"%(threadId,A)) 18 lock.release() #修改后释放 19 20 21 22 if __name__ == '__main__': 23 A = 100 #设置一个全局变量 24 lock = threading.Lock() #生成全局锁 25 thread_list = [] 26 for i in range(1,101): 27 t = threading.Thread(target=subtraction,args=[i,]) 28 thread_list.append(t) 29 t.start() 30 31 for t in thread_list: 32 t.join() 33 34 print("变量A最后获得的结果:%s"%A)
上面加锁后就不用担心切换了.
3.2 递归锁
紧接着我们来想下,如果有那么一个需求,一个子线程中调用另外两个子线程.要求两个子线程运行完成后,才继续执行.这个和mysql事务性有点像.处理完查询,在处理新添加.整个完成才能进行其他操作.
实例代码如下:
1 import threading,time 2 3 def run1(): 4 print("grab the first part data") 5 lock.acquire() 6 global num 7 num +=1 8 lock.release() 9 return num 10 def run2(): 11 print("grab the second part data") 12 lock.acquire() 13 global num2 14 num2+=1 15 lock.release() 16 return num2 17 def run3(): 18 lock.acquire() 19 res = run1() 20 print('--------between run1 and run2-----') 21 res2 = run2() 22 lock.release() 23 print(res,res2) 24 25 26 if __name__ == '__main__': 27 28 num,num2 = 0,0 29 lock = threading.RLock() 30 for i in range(10): 31 t = threading.Thread(target=run3) 32 t.start() 33 34 while threading.active_count() != 1: 35 print(threading.active_count()) 36 else: 37 print('----all threads done---') 38 print(num,num2)
所以,建议所有的锁,咱都用递归锁,不然加多层锁后会无线循环下去,因为python解释器会不知道锁的位置.
3.3 Semaphore(信号量)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
代码如下:
1 import threading,time 2 3 def run(n): 4 semaphore.acquire() 5 time.sleep(1) 6 print("run the thread: %s\n" %n) 7 semaphore.release() 8 9 if __name__ == '__main__': 10 11 num= 0 12 semaphore = threading.BoundedSemaphore(5) #最多允许5个线程同时运行 13 for i in range(20): 14 t = threading.Thread(target=run,args=(i,)) 15 t.start() 16 17 while threading.active_count() != 1: 18 pass #print threading.active_count() 19 else: 20 print('----all threads done---') 21 print(num)
执行结果就是:5个一起并行,紧接着在5个
4.将线程变为守护进程
Daemon 守护进程的作用:守护程序后台运行,如果守护程序退出了,前端的程序也就跟着死掉了.
我们在join()知识点里知道,正常生成线程的函数或者代码执行在不用join的方法前提下,只是主线程和子线程同步,没有实现子线程执行完成后在执行主线程,加了join方法后,就实现了主线程会等待子线程执行完后记性执行主线程.
那现在我们的守护进程,想实现,主线程只要关闭了,子线程也跟着关闭,不管子线程是否执行完成.
什么情况下会用到呢,真实场景是什么呢:
比如我想写一个批量登录 100台主机,执行一个命令.但是呢会有3~5台ssh连接特别慢,超时2分钟.这时候我为了执行效率,代码设定超过30秒程序就退出了.
代码为例:
1 #!/usr/bin/env python3.5 2 #__author__:'ted.zhou' 3 ''' 4 守护进程实例 5 守护进程作用是,守护进程后台运行,当守护进程死掉,后台的程序也都会死掉 6 ''' 7 8 import threading 9 import time 10 11 def run(n): 12 print('进程--%s----running-----'%n) 13 time.sleep(2) 14 print('进程--%s-----done-------'%n) 15 16 def main(): 17 ''' 18 在main中启动多个线程调用run()方法 19 :return: 20 ''' 21 for i in range(1,6): 22 t = threading.Thread(target=run,args=[i,]) 23 t.start() 24 print('进程--%s----start----- '%(t.getName())) 25 print('-----守护进程执行结束 -------') 26 27 m = threading.Thread(target=main,args=[]) 28 m.setDaemon(True) # 把这个m线程设置成守护线程 29 m.start() 30 #m.join(timeout=2) # 这里先注释,不使用join()方法的前提下,线程都不运行完就结束了 31 print("-----main thread done-----") 32 33 执行结果 34 -----main thread done----- 35 进程--1----running----- 36 37 Process finished with exit code 0
上面的结果虽然实现了主进程一旦关闭,子进程也不运行了.
但是设置了m.setDaemon(True)后,程序一下子就执行完了,不说那5个调用run函数的线程了,连main线程也没执行.
这就是m.setDaemon(True)后的作用.那么我们肯定是希望main能运行的.所以就需要把#m.join(timeout=2) 注释去掉.
去掉后的执行结果是:
1 进程--1----running----- 2 进程--Thread-2----start----- 3 进程--2----running----- 4 进程--Thread-3----start----- 5 进程--3----running----- 6 进程--Thread-4----start----- 7 进程--4----running----- 8 进程--Thread-5----start----- 9 进程--5----running----- 10 进程--Thread-6----start----- 11 -----守护进程执行结束 ------- 12 -----main thread done-----
这个结果我们看到,调用main()函数的线程执行完了.但调用run()函数的线程为什么没执行完成.我m.join(timeout=2)不是设置了2秒的等待吗?
错:设置m.join(timeout=2)方法,是告诉启用m线程的主线程可以等待m线程实例2分钟,但是实际m线程调用的main()函数根本不需要2秒钟就执行完了.
所以不到两秒的时间,主程序又开始往下走.那么我们如何实现(比如我想写一个批量登录 100台主机,执行一个命令.但是呢会有3~5台ssh连接特别慢,超时2分钟.这时候我为了执行效率,代码设定超过30秒程序就退出了.)
等待30秒的实例呢.
我们想既然m.join(timeout=2)这里m线程不需要2秒,他就执行完成了.那我把main()方法执行的时间调整到需要两秒不就OK了.
所以代码写成如下:
1 #!/usr/bin/env python3.5 2 #__author__:'ted.zhou' 3 ''' 4 守护进程实例 5 守护进程作用是,守护进程后台运行,当守护进程死掉,后台的程序也都会死掉 6 ''' 7 8 import threading 9 import time 10 11 def run(n): 12 print('进程--%s----running-----'%n) 13 time.sleep(2) 14 print('进程--%s-----done-------'%n) 15 16 def main(): 17 ''' 18 在main中启动多个线程调用run()方法 19 :return: 20 ''' 21 for i in range(1,6): 22 t = threading.Thread(target=run,args=[i,]) 23 t.start() 24 print('进程--%s----start----- '%(t.getName())) 25 time.sleep(2) # 这里设置main函数执行也需要2秒 26 print('-----守护进程执行结束 -------') 27 28 m = threading.Thread(target=main,args=[]) 29 m.setDaemon(True) 30 m.start() 31 m.join(timeout=2) # 那么这里的等待2秒就有意义了. 32 print("-----main thread done-----") 33 代码执行结果: 34 进程--1----running----- 35 进程--Thread-2----start----- 36 进程--2----running----- 37 进程--Thread-3----start----- 38 进程--3----running----- 39 进程--Thread-4----start----- 40 进程--4----running----- 41 进程--Thread-5----start----- 42 进程--5----running----- 43 进程--Thread-6----start----- 44 -----main thread done----- 45 进程--1-----done------- 46 进程--3-----done------- 47 进程--5-----done------- 48 -----守护进程执行结束 ------- 49 进程--4-----done------- 50 51 Process finished with exit code 0
这样就能满足,在30秒内执行的线程都执行完成,过了30秒还没执行完成的也不等待了.直接放弃.
守护线程完
5.Event事件
通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。
代码如下:
1 import threading,time 2 import random 3 def light(): 4 if not event.isSet(): 5 event.set() #wait就不阻塞 #绿灯状态 6 count = 0 7 while True: 8 if count < 10: 9 print('\033[42;1m--green light on---\033[0m') 10 elif count <13: 11 print('\033[43;1m--yellow light on---\033[0m') 12 elif count <20: 13 if event.isSet(): 14 event.clear() 15 print('\033[41;1m--red light on---\033[0m') 16 else: 17 count = 0 18 event.set() #打开绿灯 19 time.sleep(1) 20 count +=1 21 def car(n): 22 while 1: 23 #time.sleep(random.randrange(10)) 24 time.sleep(1) 25 if event.isSet(): #绿灯 26 print("car [%s] is running.." % n) 27 else: 28 print("car [%s] is waiting for the red light.." %n) 29 event.wait() 30 if __name__ == '__main__': 31 event = threading.Event() 32 Light = threading.Thread(target=light) 33 Light.start() 34 for i in range(3): 35 t = threading.Thread(target=car,args=(i,)) 36 t.start()
即便是在但线程下,运行速度也明显比串行线路更快.只要一到IO和sleep的时候python解释器就会切换GIL,
那么什么时候用多线程呢?IO密集型的,比如说socket链接.同时接收多个并发,多个连接,咱们的socketserver就是多线程.但是即便再快也没有真正的多线程跑得快,但是没办法这就是cpython解释器特性.
如果公司买的16核的机器,只能单核上跑100%,不太好,你想利用多核的优势,怎么办呢?只能用到多进程,cpython的GIL你想绕过只能使用多进程.多进程为什么能绕过?怎么就能利用多核优势.
因为python的多进程是用的原生进程.也就是直接是操作系统维护的.python只是调用操作系统接口,生成了一个进程.所以,所以操作系统自己会分发到哪个核心执行哪个进程.所以想绕过GIL就要使用多进程.
5.进程
python multiprocessing模块
1.语法
和多线程模块python threading模块 调用方式一差不多:
代码如下:
1 #!/usr/bin/env python3.5 2 #__author__:'ted.zhou' 3 ''' 4 想绕过CPYTHON GIL全局解释性锁,使用服务器上多核的优势,就得使用多进程. 5 cpython通过调用操作系统得接口,生成那么一个进程. 操作系统内部会分配哪个线程到哪个CPU核心去处理. 6 所以编程中使用多进程得方式还是比较多得. 7 多进程得语法 还是比较简单得,和多线程模块threading模块得调用方式1差不多. 8 1.现有一个多进程需要调用得函数 9 2.主进程里生成多个进程,指定进程运行得函数 10 3.启动进程 11 ''' 12 13 from multiprocessing import Process 14 import time 15 16 def f(name): 17 # time.sleep(2) 18 print('hello',name) 19 time.sleep(2) 20 21 if __name__ == '__main__': 22 p = Process(target=f,args=('bob',)) 23 p2 = Process(target=f,args=('ted',)) 24 p.start() 25 p2.start() 26 p.join() 27 执行结果: 28 hello bob 29 hello ted 30 31 Process finished with exit code 0
线程得基本用法就是那么简单...
那好,我先看看进程之间得关系怎么看呢.我要看主进程得ID,子进程得ID
我们可以使用os模块下得os.getppid()获得父进程得ID,使用os.getpid获得当前进程得ID
事例代码如下:
1 #!/usr/bin/env python3.5 2 #__author__:'ted.zhou' 3 ''' 4 查看进程ID得代码实例,需要使用OS模块下得getppid()方法和getpid()方法 5 ''' 6 import os 7 from multiprocessing import Process 8 9 def info(title): 10 ''' 11 定义查看父进程和当前进程的方法 12 :param title: 13 :return: 14 ''' 15 print(title) 16 print('module name:',__name__) 17 print('parent process:',os.getppid()) 18 print('process id:',os.getpid()) 19 print("\n\n") 20 21 def f(name): 22 ''' 23 随便定义一个函数,在函数中引用info()方法,查看调用此函数的进程ID,和父进程的ID 24 :param name: 25 :return: 26 ''' 27 info('\033[31;1m function f\033[0m') 28 print('hello',name) 29 30 if __name__ == '__main__': 31 info('\033[32;1mmain process line\033[0m') #首先查看当前进程的ID和父进程ID 32 p = Process(target=f, args=('bob',)) # 在当前进程下创建一个子进程,子进程调用f函数 33 p.start() # 启动子进程 34 p.join() # 等待子进程执行完成 35 执行结果如下: 36 main process line 37 module name: __main__ 38 parent process: 1809 # 主进程的父进程,这里其实就是PyCharm进程ID 39 process id: 3062 # 主进程ID 40 41 42 43 function f 44 module name: __main__ 45 parent process: 3062 # 主进程的ID 46 process id: 3063 # 子进程ID 47 48 hello bob
注释:在Linux中任何一个进程都有父进程,除了进程1代表操作系统进程.其他进程都有父进程
2.进程间通讯
2.1不同进程间内存是不共享的,要想实现两个进程间的数据交换(数据传递),可以用以下方法:
多进程的Queues
使用方法跟threading里的queue差不多
代码如下
1 #/usr/bin/env python3.5 2 #__author__:'ted.zhou' 3 ''' 4 我们知道进程之间不能互相访问内存(能访问就混乱了), 5 如果我们现在需要子进程将处理结果传递给父进程应该如何操作.那么就用到队列. 6 子进程在函数中调用队列,将数据存入.父进程从队列中读取 7 ''' 8 from multiprocessing import Process, Queue 9 10 def f(q): 11 q.put([42, None, 'hello']) 12 13 if __name__ == '__main__': 14 q = Queue() 15 p = Process(target=f, args=(q,)) # 调用 f 函数,并将队列的实例q作为参数传入 16 p.start() 17 print(q.get()) # prints "[42, None, 'hello']" 获取子进程put的值 18 p.join() 19 执行结果 20 [42, None, 'hello'] 21 Process finished with exit code 0
注释:
Queues队列遵循 先进先出原则,如果两个子进程都传入值,那么父进程先获取先put的值.同时父进程是不知道值是从哪个子进程put的.
Pipes 模块,也可以实现Queues的功能,它可以想像成管道,父进程拿一端,子进程拿一端.
实例中使用场景不多,没有Queues多.但你要知道.
代码实例如下:
1 from multiprocessing import Process, Pipe 2 3 def f(conn): 4 conn.send([42, None, 'hello']) #调用send()方法 5 conn.close() # 子进程关闭管道,父进程不需要关闭 6 7 if __name__ == '__main__': 8 parent_conn, child_conn = Pipe() #生成两端,一端给子进程,一端给父进程 9 p = Process(target=f, args=(child_conn,)) 10 p.start() 11 print(parent_conn.recv()) # prints "[42, None, 'hello']" 12 p.join() 13 执行结果: 14 [42, None, 'hello'] 15 16 Process finished with exit code 0
注释,和Queues差不多,语法上不一样
2.2 上面使用Queues\Pipe 模块实现了不同进程间的数据传递.
这不叫数据共享,数据共享,是多个进程可以同时对一个内存块进行处理,比如字典\列表等
如果要实现多个进程对一个字典或列表进行操作,应该使用Manager模块
支持共享的数据类型有:
list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array.
拿字典和列表举例:
1 #!/usr/bin/env python3.5 2 #__author__:'ted.zhou' 3 ''' 4 不同进程间的数据共享 5 ''' 6 from multiprocessing import Process, Manager 7 8 def f(d, l): 9 d[1] = '1' 10 d['2'] = 2 11 d[0.25] = None 12 l.append(1) 13 print(l) 14 15 if __name__ == '__main__': 16 with Manager() as manager: 17 d = manager.dict() 18 19 l = manager.list(range(5)) 20 p_list = [] 21 for i in range(10): 22 p = Process(target=f, args=(d, l)) 23 p.start() 24 p_list.append(p) 25 for res in p_list: 26 res.join() 27 28 print(d) 29 print(l) 30 执行结果: 31 [0, 1, 2, 3, 4, 1] 32 [0, 1, 2, 3, 4, 1, 1] 33 [0, 1, 2, 3, 4, 1, 1, 1] 34 [0, 1, 2, 3, 4, 1, 1, 1, 1] 35 [0, 1, 2, 3, 4, 1, 1, 1, 1, 1] 36 [0, 1, 2, 3, 4, 1, 1, 1, 1, 1, 1] 37 [0, 1, 2, 3, 4, 1, 1, 1, 1, 1, 1, 1] 38 [0, 1, 2, 3, 4, 1, 1, 1, 1, 1, 1, 1, 1] 39 [0, 1, 2, 3, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1] 40 [0, 1, 2, 3, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 41 {0.25: None, 1: '1', '2': 2} 42 [0, 1, 2, 3, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 43 44 Process finished with exit code 0
2.3 进程同步(进程锁lock)
我们知道每一个进程都是相互独立的,那为啥还要lock呢.
有一个场景,屏幕输出,如果很多进程都打印输出,这尼玛同时输出,还不乱了套了.所以要加锁,把某一段的代码一起输出.
代码如下:
1 from multiprocessing import Process, Lock 2 3 def f(l, i): 4 l.acquire() 5 try: 6 print('hello world', i) 7 finally: 8 l.release() 9 10 if __name__ == '__main__': 11 lock = Lock() 12 13 for num in range(10): 14 Process(target=f, args=(lock, num)).start()
3. 进程池
我们前面2.1中说明了多进程启动方法,但是现在告诉你,实际编程中,嘿嘿,用不到,用到的是进程池,所以进程池很重要.
我们知道启动一个线程几乎不耗成本,因为它共享进程的内存.而启动一个进程,就相当与复制了主进程的内存一份.
如果启动的进程过多,内存占用就多,那还不如不用,所以我们要限制进程数.怎么限制呢,使用进程池Pool模块.
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:
apply
apply_async
具体实例代码如下:
1 #!/usr/bin/env python3.5 2 #__author__:'ted.zhou' 3 ''' 4 进程池的写法:进程池有两个写法 5 ''' 6 from multiprocessing import Process,Pool 7 import time 8 9 def Foo(i): 10 time.sleep(2) 11 return i+100 12 13 def Bar(arg): 14 print('-->exec done:',arg) 15 16 pool = Pool(5) 17 18 for i in range(10): 19 pool.apply_async(func=Foo,args=[i,],callback=Bar) # 异步处理,callback是把前面一个函数执行后return的结果自动作为参数作为Bar函数的参数传入,并执行Bar函数 20 #pool.apply(func=Foo,args=(i,)) # apply为同步方法,如果采用的是apply方法调用,就不能使用callback参数回调.并且程序变成串行. 21 print('end') 22 pool.close() 23 pool.join() #进程池中进程执行完毕后再关闭,如果注释那么程序直接关闭.并且语法必须是再执行pool.close()方法后再写pool.join()方法,语法就是这样的. 24 #可以把pool.close()关闭的不是进程池,关闭是进程池启动进程的接口.所以,更明确的写法就是再for循环后,就调用pool.close()
到此,python进程内容完结