1、多任务概念
多任务处理是指用户可以在同一时间内运行多个应用程序,每个应用程序被称作一个任务.Linux就是一个支持多任务的操作系统,比起单任务系统它的功能增强了许多.
当多任务操作系统使用某种任务调度策略允许两个或更多进程并发共享一个处理器时,事实上处理器在某一时刻只会给一件任务提供服务。因为任务调度机制保证不同任务之间的切换速度十分迅速,因此给人多个任务同时运行的错觉。多任务系统中有3个功能单位:任务、进程和线程。
当操作系统使用某种策略允许两个或更多进程并发共享一个CPU时,它称作多任务运行,或多道程序运行.在规定的时间片过期或某些事件发生前,一直执行某个进程.然后,操作系统切换到另一个进程.这种切换十分迅速,给人一种这些进程是同时执行的错觉.而事实上,同一时刻在一个CPU上只能激活一个进程.这种进程间的切换在所有进程完成前一直进行.并发共享策略决定何时切换进程.该策略由操作系统或其他进程强制执行.
2、fork创建子进程
2.1.进程的概念
计算机程序只是存储在磁盘上的可执行二进制(或者其他类型)文件。只有把它们加载到内存中并被操作系统调用,才能拥有生命周期。进程则是一个执行中的程序。每个进程都拥有自己的地址空间,内存,数据栈以及其他用于跟踪执行的辅助数据。操作系统管理其上所有进程的执行,并为这些进程合理地分配时间。进程也可以通过派生(fork或spawn)新的进程来执行其他任务,不过因为每个新进程也都拥有自己的内存和数据栈等,所以只能采用进程间通信(IPC)的方式共享信息。
2.2.fork
Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程:
# cat 01-fork.py
import os
import time
#注意,fork函数,只在Unix/Linux/Mac上运行,windows不可以
ret = os.fork()
if ret == 0:
while True:
print("---1---")
time.sleep(1)
else:
while True:
print("---2---")
time.sleep(1)
运行结果:
# python 01-fork.py
---2---
---1---
---2---
---1---
---2---
---1---
---2---
---1---
---2---
---1---
...
2.3.fork系统调用
# cat test.py
import os
ret = os.fork()
print(ret)
执行结果:
#python test.py
505
0
调用一次fork()函数会有两个返回值
- 如果成功创建一个子进程,对于父进程来说返回子进程id
- 如果成功创建一个子进程,对于子进程来说返回值是0
- 如果返回值是-1,说明创建失败
流程图:
父进程调用fork()系统调用,然后陷入内核,进行进程复制,如果成功:
1,则对调用进程即父进程来说返回值为刚产生的子进程pid,因为进程PCB没有子进程信息,父进程只能通过这样获得。
2,对子进程(刚产生的新进程),则返回0,这时就有两个进程在接着向下执行。如果失败,则返回0,调用进程继续向下执行
注:fork英文意思:分支,fork系统调用复制产生的子进程与父进程(调用进程)基本一样:代码段+数据段+堆栈段+PCB,当前的运行环境基本一样,所以子进程在fork之后开始向下执行,而不会从头开始执行。
注:参考:http://www.cnblogs.com/mickole/
2.4.getpid和getppid
import os
rpid = os.fork()
if rpid<0:
print("fork调用失败。")
elif rpid == 0:
print("我是子进程(%s),我的父进程是(%s)"%(os.getpid(),os.getppid()))
x+=1
else:
print("我是父进程(%s),我的子进程是(%s)"%(os.getpid(),rpid))
print("父子进程都可以执行这里的代码")
运行结果:
我是父进程(19360),我的子进程是(19361)
父子进程都可以执行这里的代码
我是子进程(19361),我的父进程是(19360)
父子进程都可以执行这里的代码
2.5.父进程和子进程的先后顺序
父子进程的执行顺序是不确定的
os.fork()创建出一个子进程后,父进程和子进程分别向下执行代码,父进程并不会因为子进程没有执行完毕而等待子进程,而是退出程序。示例如下:
import os
import time
ret = os.fork()
if ret == 0:
time.sleep(5)
print("我是子进程")
else:
print("我是父进程")
time.sleep(2)
执行结果:
[root@jranson 1-进程编程]# python 03-父子进程执行顺序.py
我是父进程
[root@jranson 1-进程编程]# 我是子进程
#这里父进程执行完毕,退出python程序,返回终端界面。
#而子进程还没执行完毕,在后续执行完打印出结果。
2.6.全局变量在多个进程之间不共享
import os
import time
g_num = 100
ret = os.fork()
if ret == 0:
print("----process-1----")
g_num += 1
print("---process-1 g_num=%d---"%g_num)
else:
time.sleep(3)
print("----process-2----")
print("---process-2 g_num=%d---"%g_num)
执行结果:
----process-1----
---process-1 g_num=101---
----process-2----
---process-2 g_num=100---
#想要完成进程间的数据共享,需要一些方法:命名管道/无名管道/共享内存/消息队列/网络等
2.7.多个fork()问题
import os
import time
ret = os.fork()
if ret==0:
print("--1--")
else:
print("--2--")
ret = os.fork()
if ret==0:
print("--11--")
else:
print("--22--")
执行结果:
--2--
--22--
--11--
--1--
--22--
--11--
3、multiprocessing模块
3.1.multiprocessing标准库
multiprocessing是python中的多进程管理包,它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在python程序内部编写的函数。该Process对象和Thread对象的用法相同,也有start(),run(),jion()方法。此外,multiprocessing包中也有Lock/Event/Semaphore/Condition类 (这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。所以,multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境。
在使用这些共享API的时候,需要注意几点:
- 在UNIX平台上,当某个进程终结之后,该进程需要被其父进程调用wait,否则进程成为僵尸进程(Zombie)。所以,有必要对每个Process对象调用join()方法 (实际上等同于wait)。对于多线程来说,由于只有一个进程,所以不存在此必要性。
- multiprocessing提供了threading包中没有的IPC(比如Pipe和Queue),效率上更高。应优先考虑Pipe和Queue,避免使用Lock/Event/Semaphore/Condition等同步方式 (因为它们占据的不是用户进程的资源)。
- 多进程应该避免共享资源。在多线程中,我们可以比较容易地共享资源,比如使用全局变量或者传递参数。在多进程情况下,由于每个进程有自己独立的内存空间,以上方法并不合适。此时我们可以通过共享内存和Manager的方法来共享资源。但这样做提高了程序的复杂度,并因为同步的需要而降低了程序的效率。
3.2.Process创建子进程
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值;
示例:
from multiprocessing import Process
#import os
from time import sleep
# 子进程要执行的代码
def run_pro(name,age,**kwargs):
print(name)
print(kwargs)
sleep(0.5)
if __name__ == '__main__':
p = Process(target=run_pro,args=('test',18),kwargs={'m':20})
print('子进程将要执行')
p.start()
sleep(1)
p.terminate()
p.join()
print('子进程结束')
运行结果:
-----------------
子进程将要执行
test
{'m': 20}
子进程结束
----------------
3.2.Process中主进程等待子进程结束才结束
from multiprocessing import Process
import time
def test():
for i in range(5):
print("---test---")
time.sleep(1)
# 在window中,调用Process()的时候需要在前面加上if __name__ = "__main__"
if __name__ == '__main__':
p = Process(target=test)
p.start()
执行结果:
---test---
---test---
---test---
---test---
---test---
Process finished with exit code 0
注意:此处的Process finished with exit code 0是主进程结束时打印的语句。
主进程等待子进程执行结束才打印。
3.3.Process子类创建子进程
通过Process创建子进程的方式除了直接通过进程类Process进行创建,直接指定target,还可以通过创建继承Process的子类,并重写run方法实现子进程的创建。
示例如下:
from multiprocessing import Process
import time
# 继承Process
class Son(Process):
def __init__(self,interval):
#这里必须调用父类的__init__方法
#因为Process类本身也有__init__方法,这个子类相当于重写了这个方法
#我们并没有完全初始化一个Process类,最好的方法就是讲继承类本身传递给
#Process.__init__方法,完成这些初始化操作
Process.__init__(self)
self.interval = interval
#重写run方法
def run(self):
print("子进程开始运行...")
start_time = time.time()
time.sleep(self.interval)
end_time = time.time()
print("子进程执行结束,耗时%0.2f秒"%(end_time-start_time))
if __name__ == '__main__':
#开辟一个新的进程实际上就是执行本进程所对应的run()方法
p1 = Son(2)
p1.start()
p1.join()
print("我是主进程")
运行结果:
----------
子进程开始运行...
子进程执行结束,耗时2.00秒
我是主进程
4、进程池
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。
初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行。
from multiprocessing import Pool
import os,time,random
def worker(msg):
t_start = time.time()
print("%s开始执行,进程号为%d"%(msg,os.getpid()))
#random.random()随机生成0~1之间的浮点数
time.sleep(random.random()*2)
t_stop = time.time()
print(msg,"执行完毕,耗时%0.2f"%(t_stop-t_start))
po=Pool(3) #定义一个进程池,最大进程数3
for i in range(0,10):
#Pool.apply_async(要调用的目标,(传递给目标的参数元祖,))
#每次循环将会用空闲出来的子进程去调用目标
po.apply_async(worker,(i,))
print("----start----")
po.close() #关闭进程池,关闭后po不再接收新的请求
po.join() #等待po中所有子进程执行完成,必须放在close语句之后
print("-----end-----")
运行结果:
----start----
0开始执行,进程号为21466
1开始执行,进程号为21468
2开始执行,进程号为21467
0 执行完毕,耗时1.01
3开始执行,进程号为21466
2 执行完毕,耗时1.24
4开始执行,进程号为21467
3 执行完毕,耗时0.56
5开始执行,进程号为21466
1 执行完毕,耗时1.68
6开始执行,进程号为21468
4 执行完毕,耗时0.67
7开始执行,进程号为21467
5 执行完毕,耗时0.83
8开始执行,进程号为21466
6 执行完毕,耗时0.75
9开始执行,进程号为21468
7 执行完毕,耗时1.03
8 执行完毕,耗时1.05
9 执行完毕,耗时1.69
-----end-----
multiprocessing.Pool常用函数解析:
- apply_async(func[, args[, kwds]]) :使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表;
- apply(func[, args[, kwds]]):使用阻塞方式调用func
- close():关闭Pool,使其不再接受新的任务;
- terminate():不管任务是否完成,立即终止;
- join():主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用;