文章目录
一、并发编程理论
1.操作系统发展史
(1)穿孔卡片技术
计算机很庞大,使用起来很麻烦,每次只能供一个使用,期间会有很多时候计算机都不工作
优势:用户独占计算机,不会出现因为资源被其他用户占用而等待的现象,但资源的利用率低,比如:程序员独占计算机 为所欲为
劣势:计算机利用率太低,浪费资源
(2)联机批处理系统
提前使用磁带一次性录入多个程序员编写的程序,然后交给计算机执行
优势:CPU工作效率有所提升,不用反复等待程序录入
(3)脱机批处理系统
极大的提升了CPU的利用率
总结:CPU提升利用率的过程
2.多到技术
在学习并发编程的过程中,不做可以刻意提醒的条件下,默认一台计算机就一个CPU(只有一个干活的人)
(1)单道技术
所有的程序排队执行,过程中不能重合
(2)多道技术
利用空闲时间提前准备其他数据,最大化提升CPU利用率
(3)多道技术详细
1.切换
计算机的CPU在两种情况下会切换(不让你用,给别人用)
1.程序有IO操作
输入/输出操作
input、time.sleep、read、write
2.程序长时间占用CPU
多个任务同时启动,CPU都要被参与程序运行,要雨露均沾
2.保存状态
CPU每次切换走之前都需要保存当前操作状态,下次切换回来基于上次的进度继续执行
一个人要同时使用五台打印机打印资料,请问如何要一个人连续使用打印机打印资料?
启动第一台打印机打印资料之后,第一台在打印过程中就可以去启动第二台打印机执行打印任务,依次类推…
3.进程理论
(1)进程的概念
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。狭义的定义:进程是正在运行的程序的实例。广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
操作系统引入进程概念的原因:从理论角度看,是对正在运行的程序过程的抽象;从实现角度看,是一种数据结构,目的在于清晰地刻画动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。
(2)进程的特征
动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制块三部分组成。
多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。
(3)进程与程序的区别
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。程序可以作为一种软件资料长期存在,而进程是有一定生命期的。程序是永久的,进程是暂时的。
简单理解为:
程序:一堆死代码(还没有运行起来)
进程:正在运行的程序(被运行起来)
注意:同一个程序执行两次,就会在操作系统中出现两个进程,所以我们可以同时运行一个软件,分别做不同的事情也不会混乱。
(4)进程调度
要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随机进行的,而是需要遵循一定的法则,由此就有了进程的调度算法。
进程的调度算法
1.FCFS(先来先服务)调度算法
是一种简短的调度算法,该算法既可用于作业调度,也可用于进程调度,FCFS算法比较有利于长作业(进程),而不利于短作业,>>>>FCFS算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)
2.短作业优先调度算法
短作业(进程)优先调度算法(SJ/PE)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度,不能保证紧迫性作业(进程)被及时处理;作业的长度只是被估算出来的。
3.时间片轮转法+多级反馈队列(目前还在用)
将时间均分>然后根据进程时间长短再分等级
等级越靠下表示耗时越长,每次分到的时间越多,但是优先级越低
FCFS——先来先服务调度算法、SPF——短进程优先调度算法、优先级调度算法、时间片轮转调度算法、最短剩余时间优先调度算法
1 时间片轮转法
时间片轮转(Round Robin,RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,几十毫秒至几百毫秒。如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度。同时,进程调度程序又去调度当前就绪队列中的第一个进程。
显然,轮转法只能用来调度分配一些可以抢占的资源。这些可以抢占的资源可以随时被剥夺,而且可以将它们再分配给别的进程。CPU是可抢占资源的一种。但打印机等资源是不可抢占的。由于作业调度是对除了CPU之外的所有系统硬件资源的分配,其中包含有不可抢占资源,所以作业调度不使用轮转法。
在轮转法中,时间片长度的选取非常重要。首先,时间片长度的选择会直接影响到系统的开销和响应时间。如果时间片长度过短,则调度程序抢占处理机的次数增多。这将使进程上下文切换次数也大大增加,从而加重系统开销。反过来,如果时间片长度选择过长,例如,一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕,则轮转法变成了先来先服务法。时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的。
在轮转法中,加入到就绪队列的进程有3种情况:
一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行。
另一种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞。当阻塞解除之后再回到就绪队列。
第三种情况就是新创建进程进入就绪队列。
如果对这些进程区别对待,给予不同的优先级和时间片从直观上看,可以进一步改善系统服务质量和效率。例如,我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列,每个队列按FCFS原则排列,各队列之间的进程享有不同的优先级,但同一队列内优先级相同。这样,当一个进程在执行完它的时间片之后,或从睡眠中被唤醒以及被创建之后,将进入不同的就绪队列。
2 多级反馈队列
前面介绍的各种用作进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式调度算法都将无法使用。
而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述。
(1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。
(2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。
(3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。
(5)进程的并行与并发
-
并行
多个进程同时执行 ,必须要有多个CPU参与 单个CPU无法实现并行,比如赛跑,两个人都在不停的往前跑;(资源够用,比如三个线程,四核的CPU ) -
并发
多个进程同时执行 ,必须要有多个CPU参与 单个CPU无法实现并行,比如赛跑,两个人都在不停的往前跑;(资源够用,比如三个线程,四核的CPU ) -
高并发
评论程序同时服务客户端数量的能力
判断下列两句话孰对孰错
我写的程序很牛逼,运行起来之后可以实现14个亿的并行量
并行量必须要有对等的CPU才可以实现(×)
我写的程序很牛逼,运行起来之后可以实现14个亿的并发量
合情合理 完全可以实现 以后我们的项目一般都会追求高并发(√)
ps:目前国内可以说是最牛逼的>>>:12306
进程三态状态装换图
在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪态、运行态和阻塞态
-
就绪态
当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。即所有的进程在被CPU执行之前都必须先进入就绪态等待。 -
运行态
执行/运行(Running)状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。 -
阻塞态
阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。即进程运行过程中出现了IO操作,阻塞态无法直接进行运行态,需要先进入就绪态。 -
创建态和终止态:进程开始和结束的状态
进程代码演示:
(6)同步与异步
在多进程并发中,一般认为所有的进程都是有异地属性的。
用来表达任务的提交方式
同步
提交完任务之后原地等待任务的返回结果 期间不做任何事
去银行办理业务,选择排队等候(一直同步等待消息的通知)
异步
提交完任务之后不愿地等待任务的返回结果 直接去做其他事 有结果自动通知
取号排队,取号之后就可以做其他事,直到被窗口喊到取的号,再去柜台办理业务
(7)阻塞与非阻塞
用来表达任务的执行状态,也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的
阻塞
进程进入阻塞态
非阻塞
程序处于就绪态和运行态。
当一个任务因调用或IO要获取某个结果,阻塞调用必须等待调用的结果,非阻塞调用则可以不必等待这个结果而使进程始终处于就绪态和运行态。
(8)综合使用
同步阻塞:效率最低,银行取号,按部就班的排队,期间啥事也不做
同步非阻塞:原地等待,可以原地做一些事
异步阻塞:提交任务去做其他事,但还是要等cpu
异步非阻塞(******)
效率最高
很多人会把同步和阻塞混淆,是因为很多时候同步操作会以阻塞的形式表现出来,同样的,很多人也会把异步和非阻塞混淆,因为异步操作一般都不会在真正的IO操作处被阻塞。
(9)进程的开始与结束
1.进程的开始
1.系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)
2.一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)
3.用户的交互式请求,而创建一个新进程(如用户双击暴风影音)
4.一个批处理作业的初始化(只在大型机的批处理系统中应用)
2.进程的结束
1.正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)
2.出错退出(自愿,python a.py中a.py不存在)
3.严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try…except…)
4.被其他进程杀死(非自愿,如kill -9)
二、在Python程序中的进程操作
1.创建进程
# 导入进程模块
from multiprocessing import Process
def index():
with open('a.txt', 'w', encoding='utf-8') as f:
f.write('helloworld')
# 开启一个进程来执行index这个函数
if __name__ == '__main__': # 在windows系统中开启进程必须写在__main__中,其他系统中不用
p = Process(target=index) # 得到一个实例化对象,还没有开启进程,只是实例化了一个进程对象
p.start() # 这才是开启进程
2.Process 类的参数
# 导入进程模块
from multiprocessing import Process
def index(name, name1, age, gender):
print(name)
print(name1)
print(age,gender)
with open('a.txt', 'w', encoding='utf-8') as f:
f.write('helloworld')
# 开启一个进程来执行index这个函数
if __name__ == '__main__': # 在windows系统中开启进程必须写在__main__中,其他系统中不用
"""
target:执行程序
name:指定参数
args:()参数
kwargs{}:关键字参数
daemon:守护进程
"""
p = Process(target=index, name='123', args=('kevin', 'jack'),
kwargs={'age': 18, 'gender': 'male'}) # 得到一个实例化对象,还没有开启进程,只是实例化了一个进程对象
p.daemon = True # 守护进程,主进程(py文件)结束,子进程也跟着结束
p.start() # 这才是开启进程
print(p.name) # 默认进程名字是Process-1
3.Process类的几个方法
"""进程的几个属性:1. 进程名 2. 进程号pid kill """
如何查看进程的名称
print(p.name) # Process-1
怎么改进程名字
p.name = '这是新的进程名'
print(p.name) # 这是新的进程名
# 如何查看进程号
print(p.pid) # process id
print(p.is_alive()) # True
p.terminate() # 杀死进程,结束任务
import time
time.sleep(1)
print(p.is_alive())
p.join() # 等待子进程的代码全部执行完毕,在走主进程的
print("主进程的代码执行完毕")
4.如何开启多进程
多进程就意味着可以同时做多个任务嘛, 一个进程做一个任务,多个进程肯定是做多个任务
from multiprocessing import Process
import time
def task(name):
# print(name)
# with open('a.txt', 'a', encoding='utf-8') as f:
# f.write('helloworld')
print("子进程")
time.sleep(1)
if __name__ == '__main__':
"""理论上你是可以一直开进程,单是你需要考虑资源的消耗情况"""
start_time = time.time()
ll = []
for i in range(10):
p=Process(target=task, kwargs={'name':'kevin'})
p.start()
# p.join() #
ll.append(p)
for j in ll:
j.join()
print("主进程, 总时间:", time.time() - start_time)
5.基于TCP协议的高并发程序
一个服务端不能够同时给多个客户端发送消息
import socket # python提供的socket模块
def task(conn):
while True:
try:
# 异常了一个bug,粘包现象
data = conn.recv(1024) # 括号里面写的是接收的字节数,最多接收1024个字节
if len(data) == 0:
continue
print(data) # 还是bytes类型
# 服务端开始给客户端也发送一个数据
conn.send(data.upper())
except Exception as e:
print(e)
break
conn.close()
from multiprocessing import Process
if __name__ == '__main__':
# 1. 买手机
# SOCK_STREAM ====> 代表的是TCP协议
# socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # udp协议
server = socket.socket() # 默认是TCP协议
# 2. 买手机卡
# '0.0.0.0' =====> 代表允许任何的ip链接
# server.bind(('0.0.0.0', 8000)) # 服务端绑定一个地址
server.bind(('127.0.0.1', 8001)) # 服务端绑定一个地址
# 3. 开机
server.listen(1) # 监听,半连接池
print('服务端正在准备接收客户端消息:')
while True:
conn, client_addr = server.accept() # 接收, 程序启动之后,会在accept这里夯住,阻塞
p = Process(target=task, args=(conn,))
p.start()