python 之多进程
一 多道技术
多道技术是基于单核背景下产生的
cpu 执行过程(单核):
单道(串行):
cpu 同一时间只能执行一个任务,这个任务不结束,不能执行其他任务
多道技术:
cpu 在执行a任务时,a任务发生了 IO 操作或者是执行时间过长时,此时 cpu 可以将 a(保存状态)然后再切换去执行b任务。等到执行遇到'IO操作或者执行时间过长时',再将 cpu 执行权限交给 a,直到两个任务都完成。
多道技术分为时间和空间上的复用:
空间上的复用:
将内存划分为多个片,可以运行多个程序
时间上的复用:
切换 + 保存状态
例子:洗衣服30s,做饭50s,烧水30s
单道需要110s,多道只需要任务做长的那一个 切换节省时间
例子:边吃饭边玩游戏 保存状态
切换(cpu)分为两种情况:
1) 当执行程序遇到IO时,操作系统会将CPU的执行权限剥夺。
优点:CPU的执行效率提高
2) 当执行程序执行时间过长时,操作系统会将CPU的执行权限剥夺。
缺点:程序的执行效率低
二 并发与并行
1、并发:
在单核(一个 cpu)情况下,当执行a,b 程序时,a 先执行,当 a 遇到 IO 时,操作系统会将 a 程序状态保存并切换执行 b 程序,他们看起来像同时运行
并发是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发,(并行也属于并发)
ps:在单核情况下,不能实现并行,只能实现并发(伪并行)
2、并行:
在多核(多个 cpu)的情况下,当执行a,b 程序时,a 与 b 同时运行,他们是真正意义上的同时运行
单核下,可以利用多道技术,多个核,每个核也都可以利用多道技术(多道技术是针对单核而言的)
有四个核,六个任务,这样同一时间有四个任务被执行,假设分别被分配给了cpu1,cpu2,cpu3,cpu4,一旦任务1遇到I/O就被迫中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术。而一旦任务1的I/O结束了,操作系统会重新调用它(需知进程的调度、分配给哪个cpu运行,由操作系统说了算),可能被分配给四个cpu中的任意一个去执行
如图所示:
三 进程
1、什么进程
进程是一个资源单位
2、进程与程序
程序:一堆代码文件
进程:执行代码的过程,称之为进程
程序就是一堆躺在硬盘上的代码,是“死”的
进程则表示程序正在执行的过程,是“活”的
3、进程调度: (了解)
1) 先来先服务调度算法(了解)
比如程序 a,b,若a先来,则让a先服务,待a服务完毕后,b再服务。
缺点: 执行效率低。
2) 短作业优先调度算法(了解)
执行时间越短,则先先调度。
导致执行时间长的程序,需要等待所有时间短的程序执行完毕后,才能执行。
ps:现代操作系统的进程调度算法: 时间片轮转法 + 多级反馈队列 (知道)
3) 时间片轮转法
比如同时有10个程序需要执行,操作系统会给你10秒,然后时间片轮转法会将10秒分成10等分。
4) 多级反馈队列:
1级队列: 优先级最高,先执行次队列中程序。
2级队列: 优先级以此类推
3级队列:如果一个程序执行时间越长,cpu 执行权限的优先级就越低
当操作系统发现一个程序执行时间过长会降低该应用程序的在多级反馈队列中的优先级
4、同步与进步
同步与异步指的是:'提交任务的方式'
同步(串行):
a,b程序都要提交并执行,假如a先提交执行,b必须等a执行完毕后,才能提交任务。
任务提交之后,原地等待任务的返回结果,等待的过程中不做任何事(干等);程序层面上表现出来的感觉就是卡住了
异步(并发):
a,b程序都要提交并执行,假如 a 先提交并执行,b 无需等待 a 执行完毕,就可以提交任务。
任务提交之后,不原地等待任务的返回结果,直接去做其他事情
我提交的任务结果如何获取?
任务的返回结果会有一个异步回调机制自动处理
5、阻塞与非阻塞
阻塞与非阻塞是一种运行状态
1)阻塞(等待)
凡是遇到 IO 操作都会阻塞。
- IO:
input()
output()
time.sleep(3)
文件的读写
数据的传输
2)非阻塞(不等待):
就绪态、运行态
除了 IO 操作都是非阻塞(比如: 从1+1开始计算到100万,它只是运行时间长,操作系统将该进程返回到就绪态)
理想状态:我们应该让我们的写的代码永远处于就绪态和运行态之间切换
ps:上述概念的组合:最高效的一种组合就是异步非阻塞
6、进程的三种状态
1)就绪态:同步与异步(任务提交方式)
2)运行态:执行程序时状态,当程序的执行时间过长时-----》会将程序返回给就绪态
3)阻塞态:遇到 IO 操作
程序状态图如下所示:
四 开启进程的两种方式
1 multiprocess 模块介绍
python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了multiprocessing。
multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。
multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。
需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。
2 Process类的介绍
创建进程的类:
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
强调:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
参数介绍:
group参数未使用,值始终为None
target表示调用对象,即子进程要haha
args表示调用对象的位置参数元组,args=(1,2,'haha',)
kwargs表示调用对象的字典,kwargs={'name':'han','age':18}
name为子进程的名称
方法介绍:
p.start():启动进程,并调用该子进程中的p.run()
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
p.is_alive():如果p仍然运行,返回True
p.join([timeout]):主进程等待p终止(强调:是主进程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程(主进程)
属性介绍:
p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
p.name:进程的名称
p.pid:进程的pid
p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
3 开启进程的两种方式
注意:在windows中Process()必须放到# if name == 'main':下
在这个 if 语句中语句将不会在导入时被调用。
windows操作系统下创建进程一定要在main内创建, 因为windows下创建进程类似于模块导入的方式,会从上往下依次执行代码,此时会造成循环导入问题
linux中则是直接将代码完整的拷贝一份
方式一:
from multiprocessing import Process
import time
def task(name):
print('%s is running'%name)
time.sleep(3)
print('%s is over'%name)
if __name__ == '__main__': #作为执行程序时,才会执行
# 1 创建一个对象
p = Process(target=task, args=('jason',))
# 容器类型哪怕里面只有1个元素 建议要用逗号隔开
# 2 开启进程
p.start() # 告诉操作系统帮你创建一个子进程 异步
time.sleep(4)
print('主')
# 结果展示:
'''
jason is running
jason is over
主
'''
当前的计算机cpu是多核,执行程序是主进程,程序中创建的进程是子进程,他们是两个相互独立的进程,互不干涉。所以执行起来并没有先后顺序,是取决于计算机的硬件的(多核是并行)。
方式二:使用类的继承
# 第二种方式 类的继承
from multiprocessing import Process
import time
class MyProcess(Process):
def run(self): #注:函数方法名必须为 run
print('hello bf girl')
time.sleep(1)
print('get out!')
if __name__ == '__main__':
p = MyProcess()
p.start()
print('主')
总结:
"""
创建进程就是在内存中申请一块内存空间将需要运行的代码丢进去
一个进程对应在内存中就是一块独立的内存空间
多个进程对应在内存中就是多块独立的内存空间
进程与进程之间数据默认情况下是无法直接交互,如果想交互可以借助于第三方工具、模块
"""
join 方法
join 是让主进程等待子进程代码运行结束之后,再继续运行,不影响其他子进程的执行
from multiprocessing import Process
import time
def task(name, n):
print('%s is running'%name)
time.sleep(n)
print('%s is over'%name)
if __name__ == '__main__':
#方法一:这么写看起来,代码重复
#p1 = Process(target=task, args=('jason', 1))
#p2 = Process(target=task, args=('egon', 2))
#p3 = Process(target=task, args=('tank', 3))
#start_time = time.time()
#p1.start()
#p2.start()
#p3.start() # 仅仅是告诉操作系统要创建进程
#此时创建的三个进程是并行,无先后顺序之分
# # time.sleep(50000000000000000000)
# # p.join() # 主进程等待子进程p运行结束之后再继续往后执行
# p1.join()
# p2.join()
# p3.join()
#方法二:使用循环方式创建子进程
start_time = time.time()
p_list = []
for i in range(1, 4):
p = Process(target=task, args=('子进程%s'%i, i))
p.start()
p_list.append(p)
for p in p_list:
p.join()
print('主', time.time() - start_time)
#结果展示:
'''
进程1 is running
进程2 is running
进程3 is running
进程1 is over
进程2 is over
进程3 is over
主 3.025313138961792
'''
进程之间数据是相互隔离的
案例:
# coding:utf-8
from multiprocessing import Process
import time
money = 100
time.sleep(5)
print('执行程序。。。。')
def task():
global money
money = 500
print('子进程的money:', money)
if __name__ == '__main__':
p = Process(target=task)
p.start()
print('主进程的money:', money)
#结果展示
'''
执行程序。。。。 #程序运行是自上而下的
主进程的money: 100
子进程的money: 500
'''
由此可见,进程之间数据时相互隔离的,互不干涉