首页 > 技术文章 > 并发编程 | 操作系统发展史 | 进程

garyhtml 2022-01-13 22:12 原文

并发编程

操作系统发展史

#  并发编程其实就是操作系统的发展史(底层逻辑)
操作系统的发展史:

1、穿孔卡片时代

cpu的利用率极低

img

img

程序员将对应于程序和数据的已穿孔的纸带(或卡片)装入输入机,然后启动输入机把程序和数据输入计算机内存,接着通过控制台开关启动程序针对数据运行;计算完毕,打印机输出计算结果;用户取走结果并卸下纸带(或卡片)后,才让下一个用户上机。

手工操作方式两个特点:
  (1)用户独占全机。不会出现因资源已被其他用户占用而等待的现象,但资源的利用率低。
  (2)CPU 等待手工操作。CPU的利用不充分。

  20世纪50年代后期,出现人机矛盾:手工操作的慢速度和计算机的高速度之间形成了尖锐矛盾,手工操作方式已严重损害了系统资源的利用率(使资源利用率降为百分之几,甚至更低),不能容忍。唯一的解决办法:只有摆脱人的手工操作,实现作业的自动过渡。这样就出现了成批处理。

2、联机批处理系统

img

  主机与输入机之间增加一个存储设备——磁带,在运行于主机上的监督程序的自动控制下,计算机可自动完成:成批地把输入机上的用户作业读入磁带,依次把磁带上的用户作业读入主机内存并执行并把计算结果向输出机输出。完成了上一批作业后,监督程序又从输入机上输入另一批作业,保存在磁带上,并按上述步骤重复处理。
监督程序不停地处理各个作业,从而实现了作业到作业的自动转接,减少了作业建立时间和手工操作时间,有效克服了人机矛盾,提高了计算机的利用率。
但是,在作业输入和结果输出时,主机的高速CPU仍处于空闲状态,等待慢速的输入/输出设备完成工作: 主机处于“忙等”状态。

3、脱机批处理系统

为克服与缓解:高速主机与慢速外设的矛盾,提高CPU的利用率,又引入了脱机批处理系统,即输入/输出脱离主机控制。

img

img

卫星机:一台不与主机直接相连而专门用于与输入/输出设备打交道的。

  其功能是:

  (1)从输入机上读取用户作业并放到输入磁带上。

  (2)从输出磁带上读取执行结果并传给输出机。

  这样,主机不是直接与慢速的输入/输出设备打交道,而是与速度相对较快的磁带机发生关系,有效缓解了主机与设备的矛盾。主机与卫星机可并行工作,二者分工明确,可以充分发挥主机的高速计算能力。

脱机批处理系统:20世纪60年代应用十分广泛,它极大缓解了人机矛盾及主机与外设的矛盾。

  不足:每次主机内存中仅存放一道作业,每当它运行期间发出输入/输出(I/O)请求后,高速的CPU便处于等待低速的I/O完成状态,致使CPU空闲。

为改善CPU的利用率,又引入了多道程序系统。

多道技术

#  多道技术前提: 单核CPU  (一个核有4个cpu)

 多道技术就是:   	切换+保存状态
    
    
当第一个程序启动 : 这个程序启动时间比较长 cpu在服务阶段来了第二个程序需要启动。
第二个程序启动(这个程序启动时间短) : cpu会切换过来,保存上一个程序的执行点,这个程序执行完再去上一个程序的上一次执行点继续执行。
    
"""
CPU工作机制
	1.当某个程序进入IO状态的时候 操作系统会自动剥夺该程序的CPU执行权限
	2.当某个程序长时间占用CPU的时候 操作系统也会剥夺该程序的CPU执行权限
"""
所谓多道程序设计技术,就是指允许多个程序同时进入内存并运行。即同时把多个程序放入内存,并允许它们交替在CPU中运行,它们共享系统中的各种硬、软件资源。当一道程序因I/O请求而暂停运行时,CPU便立即转去运行另一道程序。
#  比喻说明 :
服务员在服务五桌客人,服务员会先给一桌菜单然后去服务下一桌然后给下一桌一个菜单,依次服务。然后再去第一桌服务依次服务,只是cpu的速度非常快。用户感受不到的。

这就是多道技术,同时服务于多个程序。 

img

在A程序计算时,I/O空闲, A程序I/O操作时,CPU空闲(B程序也是同样);必须A工作完成后,B才能进入内存中开始工作,两者是串行的,全部完成共需时间=T1+T2。

img

将A、B两道程序同时存放在内存中,它们在系统的控制下,可相互穿插、交替地在CPU上运行:当A程序因请求I/O操作而放弃CPU时,B程序就可占用CPU运行,这样 CPU不再空闲,而正进行A I/O操作的I/O设备也不空闲,显然,CPU和I/O设备都处于“忙”状态,大大提高了资源的利用率,从而也提高了系统的效率,A、B全部完成所需时间<<T1+T2。

多道程序设计技术不仅使CPU得到充分利用,同时改善I/O设备和内存的利用率,从而提高了整个系统的资源利用率和系统吞吐量(单位时间内处理作业(程序)的个数),最终提高了整个系统的效率。

  单处理机系统中多道程序运行时的特点:

  (1)多道:计算机内存中同时存放几道相互独立的程序;

  (2)宏观上并行:同时进入系统的几道程序都处于运行过程中,即它们先后开始了各自的运行,但都未运行完毕;

  (3)微观上串行:实际上,各道程序轮流地用CPU,并交替运行。

多道程序系统的出现,标志着操作系统渐趋成熟的阶段,先后出现了作业调度管理、处理机管理、存储器管理、外部设备管理、文件系统管理等功能。

由于多个程序同时在计算机中运行,开始有了空间隔离的概念,只有内存空间的隔离,才能让数据更加安全、稳定。

出了空间隔离之外,多道技术还第一次体现了时空复用的特点,遇到IO操作就切换程序,使得cpu的利用率提高了,计算机的工作效率也随之提高。

并发与并行

并行:多个程序同时执行(需要有多个cpu)
并发:多个程序只要看起来像同时运行即可(运行速度非常快 用户是感受不到的)
# 单核cpu时不能够实现并行的 只能够实现并发。
# 像12306可以同一时间支持几亿的用户买票,这时并行还是并发:
	这也肯定是并发并且是高并发,因为不可能有几亿个cpu同时服务几亿个用户。
	
星轨 :就是微博能够支持多少个明星出轨。
微博表示能够支持八个星轨。
# 故:星轨也能表示cpu的高并发。

进程理论

进程与程序的区别

程序:一堆代码(编写好放在哪里 死的)
进程: 正在运行的程序(实时运行的 活的)

# 就像你打开一个酷狗音乐听音乐就是进程,你不打开他他就最为程序放在硬盘中。

单核情况下的进程调度

进程调度算法演变

1.FCFS(first come first server) :先来先服务
		对于短作业不友好
如果第一个进程作业时间需要24小时 后面的进程只需要1s 2s 那这样后面的进程就非常不友好。
2.短作业优先调度算法
		对长作业不友好
如果进程比较长的在作业时,来了非常多的短作业 那短作业优先 长作业就要一直等着。这样对长作业非常的不友好。
3.时间片轮转法 + 多级反馈队列(目前最为普及的算法)
时间片轮转法 : 将每一个进程分配一个相同的时间片 让程序先运行起来 让用户感受到程序都在运转。

轮转法:每个进程都分配完时间片后,轮转回去,根据进程消耗时间片的多少来判断进程的类别。长作业就把他往后放(这就为多级反馈队列)

多级反馈队列 :根据进程消耗的时间片多少分为不同的等级 ,如果在工作长作业时 用户又打开进程,这时就停止长作业,优先分配时间片给刚打开的进程,使用户感受到程序启动起来。

进程三状态图

img

程序要想进入运行态必须先经过就绪态
(1)就绪(Ready)状态

  当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。

  (2)执行/运行(Running)状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。

  (3)阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。

img

同步与异步

##  用于描述任务的提交方式
同步:提交完任务之后原地等待任务的返回结果 期间不做任何事
异步:提交完任务之后不原地等待任务的返回结果 直接去做其他事 结果由反馈机制自动提醒
同时在洗衣服  做饭   扫地(机器人)
流程: 把衣服放入洗衣机 然后启动扫地机器人 然后去做饭 
# 总不能在洗衣机旁边等洗衣机工作完毕 再去启动扫地机器人然后等扫地机器人扫完地 再去扫地。

阻塞与非阻塞

阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的
继续上面的那个例子,不论是排队还是使用号码等待通知,如果在这个等待的过程中,等待者除了等待消息通知之外不能做其它的事情,那么该机制就是阻塞的,表现在程序中,也就是该程序一直阻塞在该函数调用处不能继续往下执行。
相反,有的人喜欢在银行办理这些业务的时候一边打打电话发发短信一边等待,这样的状态就是非阻塞的,因为他(等待者)没有阻塞在这个消息通知上,而是一边做自己的事情一边等待。

注意:同步非阻塞形式实际上是效率低下的,想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有。如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的;而异步非阻塞形式却没有这样的问题,因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换。

同步/异步与阻塞/非阻塞

  1. 同步阻塞形式

  效率最低。拿上面的例子来说,就是你专心排队,什么别的事都不做。

  1. 异步阻塞形式

  如果在银行等待办理业务的人采用的是异步的方式去等待消息被触发(通知),也就是领了一张小纸条,假如在这段时间里他不能离开银行做其它的事情,那么很显然,这个人被阻塞在了这个等待的操作上面;

  异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。

  1. 同步非阻塞形式

  实际上是效率低下的。

  想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有,如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的。

  1. 异步非阻塞形式

  效率更高,

  因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换

  比如说,这个人突然发觉自己烟瘾犯了,需要出去抽根烟,于是他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下,那么他就没有被阻塞在这个等待的操作上面,自然这个就是异步+非阻塞的方式了。

  

很多人会把同步和阻塞混淆,是因为很多时候同步操作会以阻塞的形式表现出来,同样的,很多人也会把异步和非阻塞混淆,因为异步操作一般都不会在真正的IO操作处被阻塞

创建进程

# 代码层面创建进程

from multiprocessing import Process
import time
import os


def test(name):
    print(os.getpid())   # 获取进程号
    print(os.getppid())  # 获取父进程号
    print(f'{name}正在运行')
    time.sleep(2)
    print(f'{name}运行结束')
    
if __name__ == '__main__':
    p = Process(target=test,args=('jason',))   # 生成一个进程对象
    p.start()  # 告诫操作系统开设一个新的进程    异步提交
    print(os.getpid()) 
    print('主进程')

    
"""
在windows中开设进程类似于导入模块
    从上往下再次执行代码
一定需要在__main__判断语句内执行开设进程的代码

在linux中是直接将代码完整的复制一份执行
    不需要在__main__判断语句内执行
"""


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('jason')
    p.start()
    print('主')

#  执行结果
sss
jason正在执行
jason再次执行

Process finished with exit code 0

进程的join方法

join方法 : 等待子进程执行完再执行主进程
from multiprocessing import Process
import time


def test(name, n):
    print('%s is running' % 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)

# 执行结果

1 is running
3 is running
2 is running
1 is over
2 is over
3 is over
3.190359592437744
主进程

进程之间默认无法交互

# 进程间数据是相互隔离的
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

对象方法

1.current_process   #  查看进程号
2.os.getpid()    #   查看进程号  
  os.getppid()  #  查看父进程进程号
3.进程的名字,p.name直接默认就有,也可以在实例化进程对象的时候通过关键字形式传入name=''
3.p.terminate()  # 杀死子进程 
4.p.is_alive()  # 判断进程是否存活	

#   3,4结合看不出结果,因为操作系统需要反应时间。主进程睡0.1即可看出效果

推荐阅读