(一)简介
之前程序执行都是一条腿走路,现在通过系统编程,我们将学会用多条腿走路,也就是接下来我们将要学习的两种多任务方式---进程和线程。
所谓多任务,就是同一时刻打开了多个任务。 一般情况下是如何进行多任务的呢?
在过去的单核CPU执行多任务时,操作系统轮流让各个任务交替执行,任务1执行0.01s,切换到任务2执行0.01s,再切换到任务3也执行0.01s,这样反复执行下去,由于CPU执行速度很快,所以我们感觉所有任务都在同时进行,这里面操作系统涉及到两种调度算法,分别是:时间片轮转,优先级调度。
而所谓的多核CPU其实可以理解成多个单核CPU,当我们任务数少于多核CPU的核数时,每个任务分配一个核处理,实现真正的多任务,称之为并行;当我们任务数多于核数时,操作系统也会自动地把任务一批一批的进行轮流调度到每个核上执行,称之为并发。
运行中的程序就是一个进程,所有的进程都是通过它的父进程来创建的。因此,运行起来的python程序也是一个进程,那么我们也可以在程序中再创建进程,多个进程可以实现并发效果,这时如何创建进程就需要用到python中强大的模块——multiprocess模块
仔细说来,其不是一个模块而是py中一个操作,管理进程的包。之所以叫multi是取自multiple的多功能的意思,由于其提供的子模块非常多,为方便记忆,我们将其分为四个部分:
- 创建进程部分
- 进程同步部分
- 进程池部分
- 进程之间数据共享
(二)multiprocess.process模块
1.process模块介绍
process是一个创建进程的模块,其语法为:
1 Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动) 2 3 强调: 4 1. 需要使用关键字的方式来指定参数 5 2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号 6 7 参数介绍: 8 1 group参数未使用,值始终为None 9 2 target表示调用对象,即子进程要执行的任务 10 3 args表示调用对象的位置参数元组,args=(1,2,'egon',) 11 4 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18} 12 5 name为子进程的名称
1 p.start():启动进程,并调用该子进程中的p.run() 2 p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法 3 p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁 4 p.is_alive():如果p仍然运行,返回True 5 p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程 6 7 方法介绍
1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置 2 p.name:进程的名称 3 p.pid:进程的pid 4 p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可) 5 p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可) 6 7 属性介绍
2.使用process创建模块
2.1给target调用对象,即子进程要执行的任务:
1 import os 2 import time 3 4 from multiprocessing import Process 5 def process1(): 6 print('process1:', os.getpid()) 7 time.sleep(1) 8 9 if __name__ == '__main__': 10 print(os.getpid()) # 获取当前进程id 11 p = Process(target=process1) 12 p.start() 13 14 15 》》》输出: 16 1556 #父进程id 17 process1: 6980 #子进程id
强调!一定注意,创建进程的语句(第10行)一定要写在 if __name__ == '__main__' 当中,因为在创建子进程时会自动import启动它的那个文件,而在import的时候又执行了整个文件,这样会导致无限递归创建子进程报错。
2.2使用args关键字给函数传参:
1 import os 2 import time 3 4 from multiprocessing import Process 5 def process1(n, name): 6 print('process1:', os.getpid()) 7 print('n:', n, name) 8 time.sleep(1) 9 10 if __name__ == '__main__': 11 print(os.getpid()) # 获取当前进程id 12 p = Process(target=process1, args=[1, 'Boru']) 13 p.start() 14 15 16 》》》输出: 17 10968 18 process1: 2504 19 n: 1 Boru
2.3进程和子进程:
1 import os 2 print(os.getpid()) # 当前进程 3 print(os.getppid()) # 父进程
看如下代码:
1 import os 2 import time 3 from multiprocessing import Process 4 5 6 def func(): 7 print('func', os.getpid(), os.getppid()) 8 time.sleep(1) 9 10 11 if __name__ == '__main__': 12 print(os.getpid(), os.getppid()) 13 Process(target=func).start() 14 print('*' * 20) 15 16 》》》输出: 17 17104 4736 18 ******************** 19 func 4324 17104
我们发现立马就执行了第14行,原因是因为这是一个异步程序,主程序执行时,不会等待子进程,子进程开辟内存需要时间,主进程继续运行,但是在执行结束的动作时他会等待子进程结束,除非异常退出。否则子进程就会变成一个僵尸进程,自己创建的,需要自己销毁。
总结:
1.主进程默认会等待子进程执行完毕之后才结束
2.主进程和子进程之间的代码是异步的
3.为什么主进程要等待子进程结束 回收一些子进程的资源
因为开启一个进程是有时间开销的 :操作系统响应开启进程指令,给这个进程分配必要的资源
1 import os 2 import time 3 from multiprocessing import Process 4 5 def func(): 6 print('func',os.getpid(),os.getppid()) 7 time.sleep(1) 8 9 if __name__ == '__main__': 10 print(os.getpid(), os.getppid()) 11 Process(target=func).start() 12 print('*'*20) 13 print('*' * 30) 14 time.sleep(0.5) 15 print('*' * 40) 16 17 》》》输出: 18 10424 12492 19 ******************** 20 ****************************** 21 func 4564 10424 22 ****************************************
2.4同步控制
举一个实际的例子
让子进程计算一个值,主进程必须等到子进程计算完之后,根据计算的值,来进行下一步计算
以文件为消息中间件,来完成主进程获取子进程的值,从而计算最终结果
1 import os 2 import time 3 from multiprocessing import Process 4 5 def func(exp): 6 print('func',os.getpid(),os.getppid()) 7 result = eval(exp) 8 with open('file','w') as f: 9 f.write(str(result)) # 写入的内容必须是一个字符串 10 11 if __name__ == '__main__': 12 print(os.getpid(), os.getppid()) 13 p = Process(target=func,args=['3*5']) 14 p.start() 15 ret = 5/6 16 p.join() #主线程等待子进程计算完 17 with open('file') as f: 18 result = f.read() # 读取结果 19 ret = ret + int(result) # 最终计算结果 20 print(ret) 21 22 23 》》》输出: 24 16784 12492 25 func 17304 16784 26 15.833333333333334
2.5启动多个进程
可以开启多个子进程,通过append方法:
1 import os 2 import time 3 from multiprocessing import Process 4 5 def process(n): 6 print(os.getpid(),os.getppid()) 7 time.sleep(1) 8 print(n) 9 10 if __name__ == '__main__': 11 p_lst = [] # 定义一个列表 12 for i in range(10): 13 p = Process(target=process,args=[i,]) 14 p.start() 15 p_lst.append(p) # 将所有进程写入列表中 16 for p in p_lst: 17 p.join() # 检测p是否结束,如果没有结束就阻塞直到结束,如果已经结束了就不阻塞 18 print('求和') 19 20 21 》》》输出: 22 17180 16032 23 10080 16032 24 17992 16032 25 18112 16032 26 18064 16032 27 17872 16032 28 13440 16032 29 8704 16032 30 15536 16032 31 14032 16032 32 0 33 2 34 3 35 4 36 6 37 1 38 7 39 5 40 8 41 9 42 求和
多个进程同时运行(注意,子进程的执行顺序不是根据启动顺序决定的)
是操作系统来决定的。它不一定是按照你的顺序来开启进程的。
它有自己的算法,比如开启一个进程,时间片轮转了。那么就不是顺序的。
2.6开启进程的第二种方式
第二种方式,是通过继承来实现的
必须要重写run方法,且不能改变其名字:
1 import os 2 from multiprocessing import Process 3 class Myprocess(Process): 4 def run(self): 5 print(os.getpid()) 6 print('和女主播聊天') 7 8 if __name__ == '__main__': 9 print(os.getpid()) 10 p = Myprocess() 11 p.start() # 在执行start的时候,会自动帮我们主动执行run方法 12 13 14 》》》输出: 15 11604 16 13764 17 和女主播聊天
如果想要接受参数,采用__init__方法:
1 import os 2 from multiprocessing import Process 3 class Myprocess(Process): 4 def __init__(self,name): 5 super().__init__() 6 self.name = name 7 def run(self): 8 print(os.getpid()) 9 print('%s和女主播聊天'%self.name) 10 11 if __name__ == '__main__': 12 print(os.getpid()) 13 p = Myprocess('alex') 14 p.start() # 在执行start的时候,会自动帮我们主动执行run方法 15 16 17 》》》输出: 18 10464 19 17860 20 alex和女主播聊天
如果想要接受多个参数,可按如下所示:
1 import os 2 from multiprocessing import Process 3 class Myprocess(Process): 4 def __init__(self,*args): 5 super().__init__() 6 self.args = args 7 def run(self): 8 print(os.getpid()) 9 for i in self.args: 10 print('%s和女主播聊天'%i) 11 12 if __name__ == '__main__': 13 print(os.getpid()) 14 p = Myprocess('alex','taibai') 15 p.start() 16 17 18 》》》输出: 19 17996 20 17532 21 alex和女主播聊天 22 taibai和女主播聊天
self.args 是一个元组类型,因为传了多个参数
3.进程中的数据隔离
子进程的变量不会影响主进程的变量!
1 from multiprocessing import Process 2 n = 100 # 全局变量 3 def func(): 4 global n 5 n += 1 # 修改全局变量 6 print('son : ',n) 7 8 9 if __name__ == '__main__': 10 p = Process(target=func) 11 p.start() 12 p.join() # 等待子进程结束 13 print(n) # 打印全局变量 14 15 》》》输出: 16 son : 101 17 100
4.守护进程
守护进程会随着主进程的结束而结束。
主进程创建守护进程
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止。
1 from multiprocessing import Process 2 import os,time,random 3 4 def task(): 5 print('%s is running' %os.getpid()) 6 time.sleep(2) 7 print('%s is done' %os.getpid()) 8 9 #守护进程内无法再开启子进程,否则抛出异常 10 # p = Process(target=time.sleep, args=(3,)) 11 # p.start() 12 13 if __name__ == '__main__': 14 p=Process(target=task) 15 p.daemon = True #1、必须在p.start()之前 16 p.start() 17 print('主') 18 19 20 》》》输出: 21 主
原因是:主进程程序启动执行到p子进程,由于子进程需要开辟内存空间,由于需要耗费时间,所以主进程会首先输出“主”,由于主进程执行完毕,那么守护子进程p也就被干掉了,随之主进程也就退出了
如果给上面代码加上阻塞 p.join()
if __name__ == '__main__': p=Process(target=task) p.daemon = True #1、必须在p.start()之前 p.start() p.join() print('主')
那么程序输出如下:
14732 is running 14732 is done 主
join以前也分析过,是起到阻塞作用,子进程执行完毕,才执行主进程,所以加上join
1、执行到join,是起到阻塞作用,就会执行子进程,然后执行完毕,在执行主进程
2、也可以这样理解,执行到join,由于主进程print(“主”)没有执行完,所以守护进程不会被干掉,继续执行
5.进程中的其他办法
5.1显示进程名和id:
from multiprocessing import Process def func(): print('wahaha') if __name__ == '__main__': p = Process(target=func) p.start() print(p.pid) # 进程id print(p.name) # 进程名 》》》输出: 18256 Process-1 wahaha
5.2检测当前进程是否还活着:
1 import time 2 from multiprocessing import Process 3 4 def func(): 5 print('wahaha') 6 time.sleep(3) 7 print('wahaha end') 8 9 if __name__ == '__main__': 10 p = Process(target=func) 11 p.start() 12 print(p.is_alive()) # 是否活着,返回bool值 13 time.sleep(3) 14 15 》》》输出: 16 True 17 wahaha 18 wahaha end
5.3在主进程中结束一个子进程:
1 import time 2 from multiprocessing import Process 3 4 def func(): 5 print('wahaha') 6 time.sleep(5) 7 print('wahaha end') 8 9 if __name__ == '__main__': 10 p = Process(target=func) 11 p.start() 12 print(p.is_alive()) # 是否活着,返回bool值 13 time.sleep(3) 14 p.terminate() # 在主进程中结束一个子进程 15 16 17 》》》输出: 18 True 19 wahaha