并发编程
操作系统发展史
-
穿孔卡片
-
磁带存储
联机批处理系统,节省了每个用户插入卡片的间隔时间
脱机批处理系统,输入机(硬盘) >>> 卫星机 >>> 高速磁带(内存) >>> 主机(CPU) >>> 高速磁带 >>> 卫星机 >>> 输出机(硬盘)
多道技术
-
目的
单核实现并发的效果
-
并发
看起来像同时运行的就可以称之为并发
-
并行
真正意义上的同时执行
并行肯定属于并发,但是并发不一定称之为并行
单核的计算机肯定不能实现并行(不考虑cpu的内核),但是可以实现并发!!
-
单道(串行)
任务一 >>> 任务二
-
多道技术
- 多道技术图解
- 多道技术的重点知识
- 空间上的复用
多个程序共用一套计算机硬件
- 时间上的复用
例如:洗衣服的同时,进行烧水,并且做饭
- 切换+保存状态
- 切换(CPU)分为两种情况
- 1.当一个程序遇见IO操作的时候,操作系统会剥夺该程序的CPU执行权限
作用:提高了CPU的利用率,并且不影响程序的执行效率,利用CPU的间歇处理别的事情
- 2.当程序长时间占用CPU的时候,操作系统也会剥夺该程序的CPU执行操作
作用:降低了程序的执行效率(原本时间+切换时间),但是可以多执行几个别的程序
- 1.当一个程序遇见IO操作的时候,操作系统会剥夺该程序的CPU执行权限
- 切换(CPU)分为两种情况
- 空间上的复用
- 多道技术图解
进程理论
-
程序和进程
程序就是一堆躺在硬盘上的代码,是"死"的
进程则表示程序正在执行的过程,是"活"的
-
进程的调度
1、FCFS:先来先服务,如果先来一个长作业,比如要执行24h的程序,后面只有1s的程序,这样对短作业是不友好的; 2、短作业优先调度算法:相反这样是对长作业不友好; | | 👇 3、时间片轮转法+多级反馈队列:先分配给新的多个进程相同的时间片段,之后根据进程消耗时间片多少分类执行;
进程三状态图
就绪、运行、阻塞
- 就绪态:当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。
- 运行态:当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为运行状态。
- 阻塞态:正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
程序在运行之后先进入就绪态,直到第一行代码运行的时候进入运行态,如果中间有代码导致程序夯住,就会导致阻塞态;三态会不断切换
同步异步与阻塞非阻塞
-
同步异步
描述的是任务的提交方式
- 同步:提交完任务之后原地等待任务的返回结果,期间不做任何事(消耗资源)
- 异步:提交完任务之后不原地等待任务的返回结果,直接去做其他事 ,结果由回调机制自动提醒(优化)
-
阻塞非阻塞
描述的是程序运行状态
- 阻塞:阻塞态
- 非阻塞:就绪态、运行态
-
排列组合
- 同步阻塞:这种形式效率是最低的;比如你吃饭排队,只能干等,什么都不能干;
- 异步阻塞:比如你排队吃饭,排到你了让店员喊一声你就好,这期间可以在附近买杯奶茶等(异步),但是不能离开餐厅附件(阻塞);
- 同步非阻塞:比如吃饭排队,你估计排队时间挺长的,你打开了王者荣耀,你边打游戏还得往前走关注排队情况,这样以来王者荣耀相当于一个程序,排队是一个程序,二者需要不断切换;
- 异步非阻塞:效率非常高,比如吃饭排队,你可以把排单号交给女朋友,让她排队,你去厕所(异步),拉屎(非阻塞);这样是不是不需要排队也不需要在餐厅旁边等着,这样就是异步非阻塞
异步非阻塞效率最高
开启进程的两种方法
在windows中开设进程类似于导入模块,从上往下执行代码,一定需要在__main__判断语句内执行开设进程的代码;
在linux中是直接将代码完整的复制一份执行,不需要在__main__判断语句内执行
# 代码层面创建进程
方法一:
from multiprocessing import Process
import time
import os
def test_print(name):
print(os.getpid()) # 获取进程号
print(os.getppid()) # 获取父进程号
print('%s正在运行' % name)
time.sleep(3)
print('%s已经结束' % name)
if __name__ == '__main__':
p = Process(target=test_print, args=('alan',)) # 生成一个进程对象
p.start() # 告诉操作系统开设一个新的进程,异步提交
print(os.getpid())
print('主进程')
方法二: 采用继承的方法
class MyProcess(Process):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print('%s正在运行' % self.name)
time.sleep(3)
print('%s已经结束' % self.name)
if __name__ == '__main__':
p = MyProcess('alan')
p.start() # 同样地效果,创建进程,实现异步
print('主进程')
"""
创建进程就是在内存中申请一块内存空间将需要运行的代码丢进去
一个进程对应的内存中就是一块独立的内存空间
多个进程对应的内存中就是多块独立的内存空间
进程与进程之间数据默认情况下是无法直接交互的,如果想交互可以借助于第三方工具、模块
"""
进程对象的join方法
join是让主进程的代码等待子进程运行结束之后
from multiprocessing import Process
import time
def test(name, n):
print('%s is run' % name)
time.sleep(n)
print('%s is over' % name)
if __name__ == '__main__':
p_list = []
start_time = time.time()
for i in range(1, 4):
p = Process(target=test, args=(i, i))
p.start()
p_list.append(p)
# p.join() # 串行 9s+
for p in p_list:
p.join()
print(time.time() - start_time) # 子进程
进程之间数据相互隔离
# 进程间数据是相互隔离的
from multiprocessing import Process
money = 100
def test():
global money
money = 999
if __name__ == '__main__':
p = Process(target=test)
p.start()
# 先确保子进程运行完毕了 再打印
p.join()
print(money) # 100,打印的是主进程的,子进程也修改了
进程对象的其他方法
-
方法
方法 说明 current_process 查看进程号 os.getpid() 查看进程号 os.getppid() 查看父进程进程号 terminate() 杀死子进程 is_alive() 判断进程是否存活 -
示例
mac pu -aux windows tasklist from multiprocessing import Process import os import time ''' get id ways: current_process().pid os.getpid() os.getppid() # 查看当前进程的父进程进程号 ''' # 创建方法 def test(name): print(f'{name} is running ! ') time.sleep(3) print(f'{name} is over!') if __name__ == '__main__': p = Process(target=test,args=("alan",)) p.start() p.terminate() # 杀死子进程,有一定的io操作,terminate()、is_alive()方法结合看不出结果,因为操作系统需要反应时间 time.sleep(0.1) print(p.is_alive()) # 判断是不是存活
僵尸进程与孤儿进程
-
僵尸进程
正常:进程代码运行结束之后并没有直接结束而是需要等待回收子进程资源才能结束;
僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。. 如果父进程先退出 ,子进程被init接管,子进程退出后init会回收其占用的相关资源;
通俗理解:如果我们出去露营,在回家的时候是不是需要把摆出来的行李收拾回去才可以呀,不能人嗨了东西不要了;from multiprocessing import Process import time def process_test(name): print(f'{name} is running!') time.sleep(2) print(f'{name} is over!') if __name__ == '__main__': p = Process(target=process_test,args=('alan',)) p.start() print('主线程') # 主进程执行了print输出语句已经没有代码可以执行了,但是子进程是在该进程内开设的,需要等待回收子进程的资源
-
孤儿进程
即主进程已经死亡(非正常)但是子进程还在运行
孤儿进程 一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。 孤儿进程将被init进程 (进程号为1)所收养,并由init进程对它们完成状态收集工作;
通俗的理解:“没爹没妈”这样是浪费资源的,没有主进程回收子进程,最后会由操作系统linit回收;