一. 操作系统
1. 计算机的硬件组成
主板 固化(寄存器,是直接和cpu进行交互的一个硬件)
CPU 中央处理器:计算(数字计算和逻辑计算)和控制(控制所有硬件协调工作)
存储 硬盘,内存
输入设备 键盘,鼠标,话筒
输出设备 显示器,音箱,打印机
2. 操作系统
早期的计算机是以计算为核心的, 现在的计算机是以存储为核心
操作系统是一个软件, 是一个能直接操作硬件的软件.
无论什么时候操作系统的目标总是: 让用户用起来更加轻松
操作系统的作用: (1) 封装所有硬件接口,让各种用户使用电脑更加轻松
(2) 是对计算机内所有的资源进行合理的调度和分配
3. 多道技术:
(1) 产生背景:针对单核,实现并发
ps:
现在的主机一般是多核,那么每个核都会利用多道技术.
有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,会被调度到4个
cpu中的任意一个,具体由操作系统调度算法决定.
(2) 空间上的复用:如内存中同时有多道程序.
(3) 时间上的复用:复用一个cpu的时间片.
强调:遇到io切,占用cpu时间过长也切,核心在于切之前将进程的状态保存下来,这样
才能保证下次切换回来时,能基于上次切走的位置继续运行.
二. 进程的理论
进程即程序正在执行的一个过程。进程是对正在运行的程序的一个抽象, 是系统进行资源分配和调度的基本单位,是操作系统结构的基础。。进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一。操作系统的其他所有内容都是围绕进程的概念展开的.
1. 什么是进程
(1) 进程的概念
第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。
文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。 第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。[3] 进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序
设计操作系统都建立在进程的基础上。
(2) 进程的特征
动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制块三部分组成。
多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。
(3) 进程与程序的区别
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
程序可以作为一种软件资料长期存在,而进程是有一定生命期的。
程序是永久的,进程是暂时的。
注意:同一个程序执行两次,就会在操作系统中出现两个进程,所以我们可以同时运行一个软件,分别做不同的事情也不会混乱.
2. 进程的调度
要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随机进行的,而是需要遵循一定的原则,由此就有了进程的调度算法。
(1) 先来先服务调度算法
先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。由此可知,
本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)。
(2) 短作业优先调度算法
短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度。但其对长作业不利;不能保证紧迫性作业(进程)
被及时处理;作业的长短只是被估算出来的
(3) 时间片轮转法
时间片轮转(Round Robin,RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,
几十毫秒至几百毫秒。如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度。同时,
进程调度程序又去调度当前就绪队列中的第一个进程。 显然,轮转法只能用来调度分配一些可以抢占的资源。这些可以抢占的资源可以随时被剥夺,而且可以将它们再分配给别的进程。CPU是可抢占资源的一种。但打印机等资源是不可抢占的。
由于作业调度是对除了CPU之外的所有系统硬件资源的分配,其中包含有不可抢占资源,所以作业调度不使用轮转法。 在轮转法中,时间片长度的选取非常重要。首先,时间片长度的选择会直接影响到系统的开销和响应时间。如果时间片长度过短,则调度程序抢占处理机的次数增多。这将使进程上下文切换次
数也大大增加,从而加重系统开销。反过来,如果时间片长度选择过长,例如,一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕,则轮转法变成了先来先服务法。时间片长度
的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的。 在轮转法中,加入到就绪队列的进程有3种情况: 一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行。 另一种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞。当阻塞解除之后再回到就绪队列。 第三种情况就是新创建进程进入就绪队列。 如果对这些进程区别对待,给予不同的优先级和时间片从直观上看,可以进一步改善系统服务质量和效率。例如,我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的
阻塞原因分成不同的就绪队列,每个队列按FCFS原则排列,各队列之间的进程享有不同的优先级,但同一队列内优先级相同。这样,当一个进程在执行完它的时间片之后,或从睡眠中被唤醒
以及被创建之后,将进入不同的就绪队列。
(4) 多级反馈队列
前面介绍的各种用作进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式
调度算法都将无法使用。 而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的
系统中,调度算法的实施过程如下所述。 (1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各
不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长
一倍。 (2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时
尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一
个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。 (3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进
入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进
程。
3. 进程的并行与并发
并行 : 并行是指两者同时执行,比如有两条车道,在某一个时间点,两条车道上都有车在跑;(资源够用,比如三个线程,四核的CPU )
并发 : 并发是指资源有限的情况下,两者交替轮流使用资源,比如只有一条车道(单核CPU资源),那么就是A车先走,在某个时刻A车退出把道路让给B走,B走完继续给A , 交替使用,目的是提高效率。
区别:
并行 : 是从微观上,也就是在一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器。
并发 : 是从宏观上,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个session。
注意:早期单核CPU时候,对于进程也是微观上串行(站在cpu角度看),宏观上并行(站在人的角度看就是同时有很多程序在执行)。
4. 同步异步,阻塞非阻塞
(1) 进程的状态
在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入三个状态:就绪,运行和阻塞。
(1)就绪(Ready)状态 : 当进程已分配到除CPU以外的所有必要的资源,只要获得cpu便可立即执行,这时的进程状态称为就绪状态。
(2)执行/运行(Running)状态 : 当进程已获得cpu,其程序正在cpu上执行,此时的进程状态称为执行状态。
(3)阻塞(Blocked)状态 : 正在执行的进程,由于等待某个事件发生而无法执行时,便放弃cpu而处于阻塞状态, 此时,进程还在内存中。引起进程阻塞 的事件可有多种,例如,等待I/O完成(input)、申请缓冲区不能满足、等待信件(信号)等。
特殊状态 : 是指因为种原因,进程放弃了cpu,导致进程无法继续执行,此时进程被踢出内存。
(2) 同步和异步
同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,
两个任务的状态可以保持一致。
异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是
否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。
(3) 阻塞和非阻塞
阻塞就是做一件事情立刻得到了回答
非阻塞就是你做一件事,但没有立刻得到回答,需要等待
(4) 同步异步与阻塞非阻塞
1) 同步阻塞形式
效率最低. 你在排队等电梯,什么什么事也不做
2) 异步阻塞形式
在电梯那排队站着, 不用一直看电梯门开没开, 听到电梯门响,就知道电梯门开了.
3) 同步非阻塞形式
在排队等待电梯时, 出去打个电话, 隔一段时间来看一下电梯有没有开门.
4) 异步非阻塞形式
效率最高
三. 进程的操作
1. 开启进程的方式
p = Process(target=函数名, args=(参数,)) args=((,)) 里面传参是以元组形式
from multiprocessing import Process import time import os ############################################开启子进程的一种方式 def func(i): time.sleep(1) print('这里是儿子进程,儿子自己的pid是%s,儿子的父进程的pid是%s'%(os.getpid(),os.getppid())) # os.getpid()获取的是当前进程自己的pid # os.getppid()获取的是当前进程的父进程的pid if __name__ == '__main__': p = Process(target=func,args=(1,))# 实例化一个进程对象 p.start()# 开启一个子进程 print('这里是父亲进程,父进程自己的pid是:%s,父亲的父亲的pid是%s'%(os.getpid(),os.getppid())) ######################################### 开启子进程的另外一种方式,以继承的方式 class MyProcess(Process): def __init__(self): super(MyProcess, self).__init__() def run(self): print('这是以继承类的方式开启的子进程') if __name__ == '__main__': p1 = MyProcess() p1.start()# 是指,解释器告诉操作系统,去帮我开启一个进程, 就绪状态 # p1.run()# 告诉操作系统,现在马上帮我执行这个子进程 执行
开启多进程:
from multiprocessing import Process import time import random def func(i): print('我是%s'%i) if __name__ == '__main__': l = [] addr = ['河南的','山东的','辽宁的','湖南的'] for i in addr: p = Process(target=func,args=(i,)) p.start() # p.join() l.append(p) [p.join() for p in l] time.sleep(1) print('我选%s'%(random.choice(addr)))
2. 进程的方法
进程的方法: (1) start() 开启一个子进程, 底层调用的是p.run() (2) join() 异步变同步(就是让父进程停留在join这句话,等待子进程执行结束,父进程再继续执行) (3) is_alive() 判断进程是否活着 (4) terminate() 杀死进程
from multiprocessing import Process import time def func(): for i in range(500): time.sleep(0.01) print('儿子在这里') if __name__ == '__main__': p = Process(target=func) p.start() p.join()# 是让主进程等待子进程执行完。 现象:主进程执行到这句话,主进程阻塞住,等待子进程执行 # time.sleep(1) for i in range(100): time.sleep(0.01) print('爸爸在这里')
from multiprocessing import Process import time def func(): time.sleep(1) print(123) if __name__ == '__main__': p = Process(target=func,) p.start() p.terminate()# 杀死p进程,让解释器告诉操作系统,请杀掉p进程。 print('子进程是否还活着?', p.is_alive()) time.sleep(0.002) print('子进程是否还活着?', p.is_alive()) # 返回一个bool值,如果返回True,代表进程还活着,如果返回False,代表子进程死了
3. 进程的属性
进程的常用属性 (1) p.name = 给p进程一个名字 (2) p.pid 返回p进程的pid (3) p.daemon = True 将p进程设置为守护进程。(True为守护进程,False为普通进程) 守护进程的两个特点: 守护进程会随着父进程的代码执行完毕而结束 守护进程不能再创建子进程
from multiprocessing import Process import os def func(): print('这里是儿子,儿子的pid是%s'%(os.getpid())) if __name__ == '__main__': p = Process(target=func) p.start() p.name = 'jack' print('儿子的名字是%s' % p.name) print('儿子的pid是%s' % p.pid) print('儿子是不是守护进程?', p.daemon)
守护进程
from multiprocessing import Process import time def func1(): time.sleep(1) print('守护进程') def func2(): time.sleep(1) print('普通进程') if __name__ == '__main__': p1 = Process(target=func1) p2 = Process(target=func2) p1.daemon = True # 把p1设为守护进程 p1.start() p2.start() p2.join() print('父进程') # 结果: # 守护进程 # 普通进程 # 父进程
四. 进程间的通信
1. 锁 : Lock
l = Lock() 实例化一把锁, 一把锁配一把钥匙
l.acquire() 拿钥匙,锁门
l.release() 还钥匙,开门
做一个买票的简单程序 10个人在同一时间去买3张票
from multiprocessing import Value, Process import time def check(i, num): print('第%s个人查到余票还剩%s张' % (i, num.value)) def buy(i, num): if num.value > 0: print('第%s个人抢到了票' % i) time.sleep(1) num.value -= 1 else: print('第%s个人没有抢到票' % i) if __name__ == '__main__': num = Value('i', 3) for i in range(10): p1 = Process(target=check, args=(i + 1, num)) p1.start() for i in range(10): p2 = Process(target=buy, args=(i + 1, num)) p2.start() # 结果: # 第2个人查到余票还剩3张 # 第3个人查到余票还剩3张 # 第6个人查到余票还剩3张 # 第7个人查到余票还剩3张 # 第1个人查到余票还剩3张 # 第5个人查到余票还剩3张 # 第10个人查到余票还剩3张 # 第4个人查到余票还剩3张 # 第8个人查到余票还剩3张 # 第9个人查到余票还剩3张 # 第1个人抢到了票 # 第2个人抢到了票 # 第3个人抢到了票 # 第4个人抢到了票 # 第5个人抢到了票 # 第8个人抢到了票 # 第9个人抢到了票 # 第6个人抢到了票 # 第7个人抢到了票 # 第10个人抢到了票
发现所有人都抢到票了,但是票却只有三张, 因为开的查票10个进程和买票10个进程都是异步执行的,买票的10个进程如果执行够快,还没有对余票数进行相应的操作,就会查到一直有余票3张,就可以买到票了.所以我们要让买票的进程一个个执行买票操作,而不是一起执行,而造成了数据混乱.
from multiprocessing import Lock, Value,Process def check(i, num): print('第%s个人查到余票还剩%s张' % (i, num.value)) def buy(i, num, l): l.acquire() # 拿走钥匙,锁上门,不允许其进程进入 if num.value > 0: print('第%s个人抢到了票' % i) num.value -= 1 else: print('第%s个人没有抢到票' % i) l.release() # 返还钥匙,其它进程可以进入一个 if __name__ == '__main__': l = Lock() num = Value('i', 3) for i in range(10): p1 = Process(target=check, args=(i + 1, num)) p1.start() for i in range(10): p2 = Process(target=buy, args=(i + 1, num, l)) p2.start() # 结果: # 第1个人查到余票还剩3张 # 第2个人查到余票还剩3张 # 第3个人查到余票还剩3张 # 第4个人查到余票还剩3张 # 第5个人查到余票还剩3张 # 第7个人查到余票还剩3张 # 第6个人查到余票还剩3张 # 第8个人查到余票还剩3张 # 第9个人查到余票还剩3张 # 第10个人查到余票还剩3张 # 第1个人抢到了票 # 第2个人抢到了票 # 第3个人抢到了票 # 第4个人没有抢到票 # 第5个人没有抢到票 # 第6个人没有抢到票 # 第7个人没有抢到票 # 第8个人没有抢到票 # 第9个人没有抢到票 # 第10个人没有抢到票
2. 信号量: Semaphore
sem = Semaphore(n)
n : 是指初始化一把锁配几把钥匙,一个int型
拿钥匙,锁门 l.acquire()
还钥匙,开门 l.release()
信号量机制比锁机制多了一个计数器,这个计数器是用来记录当前剩余几把钥匙的。
当计数器为0时,表示没有钥匙了,此时acquire()处于阻塞。
对于计数器来说,每acquire一次,计数器内部就减1,release一次,计数器就加1
from multiprocessing import Process, Semaphore import time def func(i, se): se.acquire() # 可以计入5个进程 print(i) time.sleep(5) se.release() # 还一把钥匙,进来一个进程 if __name__ == '__main__': se = Semaphore(5) # 5把钥匙 for i in range(10): # 开10个进程 p = Process(target=func, args=(i, se)) p.start()
3. 事件机制 : Event
e = Event() # e.set() #将is_set()设为True # e.clear() # 将is_set()设为False # e.wait() #判断is_set的bool值,如果bool为True,则非阻塞,bool值为False,则阻塞 # e.is_set() # 标识 # 事件是通过is_set()的bool值,去标识e.wait() 的阻塞状态 # 当is_set()的bool值为False时,e.wait()是阻塞状态 # 当is_set()的bool值为True时,e.wait()是非阻塞状态 # 当使用set()时,是把is_set的bool变为True # 当使用clear()时,是把is_set的bool变为False
from multiprocessing import Event, Process import time def traffic_light(e): e.set() # # 把e.is_set() 设置为True print('