进程的概念
可执行的代码就叫程序。
正在运行着的代码+需要的一些资源就是进程。
例子:QQ没打开的时候是程序,打开了之后是进程。
fork
fork()
调用一次,返回两次。从返回处开始,父子进程的代码就是一样的了。
意思大概是这样的:
import os
ret = os.fork()
if ret == 0:
print('我是子进程')
else:
print('我是父进程')
从ret = os.fork()
这句往下,父进程和子进程都会往下执行:
if ret == 0:
print('我是子进程')
else:
print('我是父进程')
fork()
将0
返回给子进程,将子进程的ID
返回给父进程。
获取自己进程的ID用os.getpid()
,获取父进程的ID用os.getppid()
。
用fork()
的话,父进程不会等待子进程结束。(也就是说,主进程可以在子进程结束之前结束)
进程的执行过程:
操作系统在面对多进程的时候,会根据自身的调度算法来进行执行。所以你不知道每个进程执行了多少代码后就被CPU给踢出来了。这都是操作系统自己决定的,程序员管不了。
进程的资源是独立的,所以程序中的全局变量在多进程中是不共享的。
什么意思呢?
import os
import time
nums = 1
ret = os.fork()
if not ret:
print('zijincheng')
nums += 1
else:
time.sleep(1)
print('fujincheng')
print(nums)
输出结果:
[xm@vth 桌面]$ python multi.py
zijincheng
2
fujincheng
1
我们在子进程中对nums
进行了修改,并让主线程睡了1秒来保证先执行子进程,但是我们可以看到,子进程修改了nums
的值对主进程中的nums
的值并没有任何影响。
如果想共享咋办???要进程间通信技术。
fork炸弹
while True:
os.fork()
multiprocessing-Process
很遗憾,fork()
在windows
上不能用。所以我们用fork
写不出跨平台的程序。
但是我们的python
如此强大,它肯定能写出来跨平台的多进程程序。
用multiprocessing
就可以解决这个问题。
注意,在windows
平台上用Process
写多进程时,需要在main
函数中执行(Linux中就无所谓了):
import os
from multiprocessing import Process
def pr():
print('aaa')
if __name__ == '__main__':
p1 = Process(target=pr)
p1.start()
p2 = Process(target=pr)
p2.start()
不这么写会报错。
用Process
写的多进程程序,主进程会等待子进程结束后再结束(注意,是结束,而不是执行哦)。意思就是,如果启动子进程是主线程的最后一条语句的话,我们不用care这个问题,因为主进程没有语句了,只需要等待子进程结束然后自己也结束即可。
如果开启子进程后主进程还想执行点东西,是可以的。因为主进程和子进程一起抢占CPU,主进程并不会等子进程结束之后再执行自己的代码。
如果我们现在有一个需求:主进程需要等待子进程执行完毕后再执行一些语句,那怎么办呢? 用join()
。 join()起的作用是阻塞主进程。
import os
from multiprocessing import Process
import time
def pr(i):
print('aaa%d'%i)
time.sleep(3)
if __name__ == '__main__':
p1 = Process(target=pr,args=(1,))
p1.start()
p1.join(1) # 也可以给join设置一个timeout
print('hhh')
join()
解除阻塞状态有两个可选条件:
1、timeout
超时
2、子进程运行结束
比如上面这样一个程序,子进程执行1秒后,主进程就可以输出hhh
了。
terminate()
# 直接杀死子进程。
示例:
import os
from multiprocessing import Process
import time
def pr(i): # 子进程需要执行的代码
print('aaa%d'%i)
time.sleep(3)
if __name__ == '__main__':
p1 = Process(target=pr,args=(1,))
p1.start()
p1.join(1) # 也可以给join设置一个timeout
print('hhh')
p1.terminate() # 直接强制杀掉p1这个进程
multiprocessing-继承Process类
也可以通过继承Process
类来创建进程:
只需要写一个继承自Process
的类,然后重写类的run()
方法即可。
import os
from multiprocessing import Process
import time
class Demo(Process): # 继承自Process
def run(self):
print('wocao')
if __name__ == '__main__':
d = Demo()
d.start() # 开启进程
进程池Pool
池的作用一般都是缓冲。(蓄水池、备胎池2333333333)
from multiprocessing import Pool
import time
def r(i):
print('hhh%d'%i)
time.sleep(0.5)
if __name__ == '__main__':
po = Pool(3) # 定义了一个最大进程数为3的进程池
for i in range(10):
po.apply_async(r,(i,)) # r 为要调用的目标,后面可用一个元组给出目标的参数
# 循环10次的目的是把任务加入到进程池中(for是一下子就执行完了,跟进程池没关系)
# 我们会发现输出是3个3个往外蹦,因为进程池有3个进程
po.close() # 关闭进程池 关闭后不再接收新的请求
po.join()
注意,用进程池写的程序,主线程不会等待子进程结束后再结束。而且主进程结束之后子进程也会跟着结束,并不像fork
那样,主进程结束之后子进程还能做一些事情。在Pool
中,主进程死了,子进程就没了。所以如果要想让主进程等待子进程,需要加上join()
。
而且,在po.apply_async(r,(i,))
时,进程池中的子进程就开始执行了,并不是非要等任务全部添加完成再执行。
代码:
from multiprocessing import Pool
import time
def r(i):
print('hhh%d'%i)
time.sleep(0.5)
if __name__ == '__main__':
po = Pool(3) # 定义了一个最大进程数为3的进程池
for i in range(100000):
po.apply_async(r,(i,)) # r 为要调用的目标,后面可用一个元组给出目标的参数
print(1)
# 我们会发现输出是3个3个往外蹦,因为进程池有3个进程
print('wocao')
po.close() # 关闭进程池 关闭后不再接收新的请求
po.join()
证据:
往进程池中添加的任务的数量是不限制的。
进程池就相当于开了个公司,然后招了3个小弟给你干活。来了活就交给小弟干,别管多少活都要交给小弟干。要合理的设置小弟数量(因为切换时耗费大量资源)。
po.apply_async(r,(i,))
这种方式是非阻塞方式。
接下来我们看看阻塞apply
的怎么写:
from multiprocessing import Pool
import time
def r(i):
print('hhh%d'%i)
time.sleep(0.5)
if __name__ == '__main__':
po = Pool(3)
for i in range(100000):
po.apply(r,(i,))
print('wocao')
po.close()
po.join()
其实就是把apply_async
改成了apply
。
apply会一个一个的往外蹦,并不会3个3个的往外蹦。
进程间通信Queue
我们创造出来进程,就是想让很多进程协作来完成复杂的任务,所以说进程间必须要可以通信。那怎么实现进程间通信呢?用队列Queue
。
既然我们是用Queue
来实现进程间通信的,那么我们这就来先好好学一下Queue
。
我们先来了解一下Queue的两个方法:get
和put
:
from multiprocessing import Queue
q = Queue(3)# 创建一个最大容量是3的队列
# q = Queue() # 创建一个无限容量的队列
# q.put(1)
# q.put(1)
# q.put(1)
# q.put(2) # 这句会阻塞。 因为队列满了,放不进去。
q.get() # 这句会阻塞。因为队列空了,取不出来。
然后再来看一下返回队列实际长度的qsize
方法:
from multiprocessing import Queue
q = Queue(3) # 创建一个最大容量是3的队列
q.put(2)
print(q.qsize()) # 返回队列的实际长度
然后再来看一下返回队列是空还是满的方法empty
和 full
:
from multiprocessing import Queue
q = Queue(3)
q.put(2)
print(q.empty()) # 队列为空则返回True
print(q.full()) # 队列满了返回False
所以为了避免程序处于阻塞状态,我们在使用get
和put
方法之前需要判空和判满.
当然,我们也可以配合另外两个方法put_nowait()
和 get_nowait()
,用异常处理来解决这个问题:
q.put_nowait(obj)
q.get_nowait()
这样的话异常时不会阻塞,只会抛出异常
不能有两个进程同时get
,这样会造成时序错误。
来看一个实例:
from multiprocessing import Queue,Process
import os,time,random
def write(q):
for value in ['A','B','C']:
print('put %s to queue' % value)
q.put(value)
time.sleep(random.random())
def read(q):
while True:
if not q.empty():
value = q.get()
print('get %s from queue' % value)
time.sleep(random.random())
else:
break
if __name__ == '__main__':
q = Queue()
pw = Process(target=write,args=(q,))
pr = Process(target=read, args=(q,))
pw.start()
pw.join()
pr.start()
pr.join()
输出:
put A to queue
put B to queue
put C to queue
get A from queue
get B from queue
get C from queue
刚刚我们说的都是Process
创造的多线程程序怎么用Queue
,那么进程池Pool
弄出来的多进程该怎么用Queue
呢?
应该用即可:
from multiprocessing import Manager,Pool
q = Manager().Queue()
别的东西都一样。