一、进程
现实生活中,很多场景中的事情是同时进行的,比如开车的时候是手和脚共同来驾驶汽车;再比如唱歌和跳舞也是同时进行的,试想如果唱歌和跳舞分开来做的话就不会有那么好的效果了
程序中,如下,来模拟唱歌和跳舞;
很显然,刚才的程序并没有实现唱歌和跳舞同时进行;如果要实现‘跳舞和唱歌’同时进行,那么就需要一个新方法,叫做多任务
多任务的概念:
简单来说,操作系统可以同时运行多个任务,打个比方,你一边用浏览器上网,一边用播放器听音乐,一边用word赶作业,这就是多个任务,至少有3个任务在运行,后台还有好多任务在同时运行,只是桌面没有显示而已。
过去都是单核CPU,单核CPU也可以执行多任务,由于CPU执行代码都是顺序执行的,那么单核CPU是怎么执行多任务的呢?
答案就是操作系统轮流让各个任务交替运行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,切换到任务3...这样反复执行下去,表面上看每个任务都是交替执行的,但是由于CPU执行速度太快了,我们感觉就像所有的任务在同时执行一样。
真正的并行执行多任务只能在多核CPU上实现,但是由于任务量远远大于CPU的核数,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。
1、进程的创建--fork()
进程 VS程序:
编写完的代码,在没有运行的时候,称之为程序。
正在运行的代码,称为进程
进程,除了包含代码以外,还有需要运行的环境等,是和程序有区别的。
fork()
python 的os模块封装了常见的系统调用,其中就包括fork,可以在python进程中轻松创建子进程;
说明:
程序执行到os.fork()时,操作系统会创建一个新的进程(子进程),然后复制父进程的所有信息到子进程中
然后父进程和子进程都会从fork()函数中得到一个返回值,在子进程中这个值一定为0,而父进程的这个值为子进程的ID号
getpid()、getppid()
2、多进程修改全局变量
多进程中,每个进程中所有数据(包括全局变量)都会各拥有一份,互不影响
3、多次fork 问题
如果在一个程序中,有两次fork函数调用,会产生几个进程
答案是每fork调用一次,会产生2的x次方个进程
调用一次fork产生两个进程,一个父进程,一个子进程;
调用两次fork,会产生4个进程,如下图:
父子进程的执行顺序
父进程和子进程执行顺序没有规律,完全取决于操作系统的调度算法。
4、multiprocessing
windows上没有fork调用,难道在Windows上无法用python编写多进程的程序?
multiprocessing模块就是跨平台版本的多进程模块
multiprocessing模块提供了一个Process类来代表一个进程对象,下面的例子来演示了启动一个进程并等待其结束:
说明:
创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,
join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步;
Process语法结构:
Process([group[,target[,name[,args[,kwargs]]]]])
target:表示这个进程实例调用的对象;
args:表示调用对象的位置参数元组;
kwargs:表示调用对象的关键字参数字典;
name:为当前进程实例的别名;
group:大多数情况下用不到;
Process 类常用的方法:
is_alive():判断进程实例是否还在执行;
join([timeout]):是否等待进程实例执行结束,或等待多少秒;
start():启动进程实例(创建子进程);
run():如果没有给定target 参数,对这个对象调用的start()方法时,就将执行对象中的run方法;
terminate():不管任务是否完成,立即终止;
Process类常用属性:
name:当前进程实例的别名,默认为Process-N,N从1开始递增的整数;
pid:当前进程的PID值;
实例1:
运行结果:
实例2:
运行结果:
5、进程的另一种创建方法
进程的创建-Process子类
创建新的进程还能使用类的方式,可以自定义一个类,继承Process类,每次实例化这个类的时候,就等同实例化一个进程对象,请看下面实例:
说明:
对一个不包含target属性的Process类,执行start()方法,就会运行这个类中的run方法;
6、进程池
当需要创建的⼦进程数量不多时, 可以直接利⽤multiprocessing中的Process
动态成⽣多个进程, 但如果是上百甚⾄上千个⽬标, ⼿动的去创建进程的⼯
作量巨⼤, 此时就可以⽤到multiprocessing模块提供的Pool⽅法。
初始化Pool时, 可以指定⼀个最⼤进程数, 当有新的请求提交到Pool中时,
如果池还没有满, 那么就会创建⼀个新的进程⽤来执⾏该请求; 但如果池中
的进程数已经达到指定的最⼤值, 那么该请求就会等待, 直到池中有进程结
束, 才会创建新的进程来执⾏, 请看下⾯的实例:
multiprocessing.Pool常用函数解析:
apply_async(func[args[,kwds]]):使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字列表;
apply(func[args[,kwds]]):使用阻塞方式调用func;
close():关闭进程池,使其不再接收新的任务;
terminate():不管任务是否完成,立即终止
join():主进程阻塞,等待子进程退出,必须要在close()或terminate()之后使用;
7、进程间通信
Process间有时需要通信
可以使用multiprocessing 模块的Queue实现多进程之间的的数据传递,Queue本身是一个消息队列程序;
Queue使用说明:
初始化Queue()对象时,(例如:q = Queue()),若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接收的消息数量没有上线(直到内存的上限);
Queue.qsize():返回当前队列包含的消息数量;
Queue.empty():如果队列为空,返回True,反之false;
Queue.full():如果队列满了,返回True,反之false;
Queue.get([block[,timeout]]):获取队列中的一条消息,然后将其从队列中移除,block默认值为True;
1)、如果block为默认值True,且没有设置timeout(单位秒)时间,消息队列如果为空,此时程序将被阻塞(停在读取状态),直到从消息队列读到消息为止;
如果设置了timeout,则会等待timeout秒,如还没有读取到消息,则抛出‘Queue.Empty ’异常;
2)、如果block 为false,消息队列如果为空,则会立刻抛出‘Queue.Empty’异常;
Queue.get_nowait():相当于Queue.get(False);
Queue.put(item,[block[,timeout]]):将item写入队列,block默认为True;
1)、如果block为默认值True,且没有设置timeout(单位秒)时间,如果消息队列没有空间写入,此时程序将被阻塞(停在写入状态),直到从消息队列腾出空间为止,;
如果设置了timeout,则会等待timeout秒,如还没有写入空间,则抛出‘Queue.Full ’异常;
2)、如果block 为false,消息队列没有写入空间,则会立刻抛出‘Queue.Full’异常;
Queue.put_nowait(item): 相当于 Queue.put(item,False)
实例:
在父进程中创建两个子进程,一个往Queue中写数据,一个从Queue中读数据;
进程池中的Queue:
如果要使用Pool创建进程,就需要使用 multiprocessing.Manager()中的Queue()