13 . Python3之并发编程

时间:2022-07-25
本文章向大家介绍13 . Python3之并发编程,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

什么是操作系统?

为什么要有操作系统?

现代的计算机系统主要是由一个或者多个处理器,主存,硬盘,键盘,鼠标,显示器,打印机,网络接口及其他输入输出设备组成。

  一般而言,现代计算机系统是一个复杂的系统。

  其一:如果每位应用程序员都必须掌握该系统所有的细节,那就不可能再编写代码了(严重影响了程序员的开发效率:全部掌握这些细节可能需要一万年....)

  其二:并且管理这些部件并加以优化使用,是一件极富挑战性的工作,于是,计算安装了一层软件(系统软件),称为操作系统。它的任务就是为用户程序提供一个更好、更简单、更清晰的计算机模型,并管理刚才提到的所有设备。

总结:

程序员无法把所有的硬件操作细节都了解到,管理这些硬件并且加以优化使用是非常繁琐的工作,这个繁琐的工作就是操作系统来干的,有了他,程序员就从这些繁琐的工作中解脱了出来,只需要考虑自己的应用软件的编写就可以了,应用软件直接使用操作系统提供的功能来间接使用硬件

什么是操作系统?

精简的说的话,操作系统就是一个协调、管理和控制计算机硬件资源和软件资源的控制程序。操作系统所处的位置如下图. 操作系统位于计算机硬件与应用软件之间,本质也是一个软件。操作系统由操作系统的内核(运行于内核态,管理硬件资源)以及系统调用(运行于用户态,为应用程序员写的应用程序提供系统调用接口)两部分组成,所以,单纯的说操作系统是运行于内核态的,是不准确的。

细说的话,操作系统应该分为两部分功能:

![2](C:UsersYouMenPicturesPythonPython并发编程2.png)#一:隐藏了丑陋的硬件调用接口(键盘、鼠标、音箱等等怎么实现的,就不需要你管了),为应用程序员提供调用硬件资源的更好,更简单,更清晰的模型(系统调用接口)。应用程序员有了这些接口后,就不用再考虑操作硬件的细节,专心开发自己的应用程序即可。

# 例如:操作系统提供了文件这个抽象概念,对文件的操作就是对磁盘的操作,有了文件我们无需再去考虑关于磁盘的读写控制(比如控制磁盘转动,移动磁头读写数据等细节),

#二:将应用程序对硬件资源的竞态请求变得有序化

# 例如:很多应用软件其实是共享一套计算机硬件,比方说有可能有三个应用程序同时需要申请打印机来输出内容,那么a程序竞争到了打印机资源就打印,然后可能是b竞争到打印机资源,也可能是c,这就导致了无序,打印机可能打印一段a的内容然后又去打印c...,操作系统的一个功能就是将这种无序变得有序。





# 作用一:为应用程序提供如何使用硬件资源的抽象
# 例如:操作系统提供了文件这个抽象概念,对文件的操作就是对磁盘的操作,有了文件我们无需再去考虑关于磁盘的读写控制

注意:
    操作系统提供给应用程序的该抽象是简单,清晰,优雅的。为何要提供该抽象呢?
    硬件厂商需要为操作系统提供自己硬件的驱动程序(设备驱动,这也是为何我们要使用声卡,就必须安装声卡驱动。。。),厂商为了节省成本或者兼容旧的硬件,它们的驱动程序是复杂且丑陋的
    操作系统就是为了隐藏这些丑陋的信息,从而为用户提供更好的接口
    这样用户使用的shell,Gnome,KDE看到的是不同的界面,但其实都使用了同一套由linux系统提供的抽象接口


# 作用二:管理硬件资源
    现代的操作系统运行同时运行多道程序,操作系统的任务是在相互竞争的程序之间有序地控制对处理器、存储器以及其他I/O接口设备的分配。
例如:
    同一台计算机上同时运行三个程序,它们三个想在同一时刻在同一台计算机上输出结果,那么开始的几行可能是程序1的输出,接着几行是程序2的输出,然后又是程序3的输出,最终将是一团糟(程序之间是一种互相竞争资源的过程)
    操作系统将打印机的结果送到磁盘的缓冲区,在一个程序完全结束后,才将暂存在磁盘上的文件送到打印机输出,同时其他的程序可以继续产生更多的输出结果(这些程序的输出没有真正的送到打印机),这样,操作系统就将由竞争产生的无序变得有序化。
操作系统与普通软件的区别?
# 1 . 主要区别是:你不想用暴风影音了你可以选择用迅雷播放器或者干脆自己写一个,但是你无法写一个属于操作系统一部分的程序(时钟中断处理程序),操作系统由硬件保护,不能被用户修改。

# 2 . 操作系统与用户程序的差异并不在于二者所处的地位。特别地,操作系统是一个大型、复杂、长寿的软件,

# 大型:linux或windows的源代码有五百万行数量级。按照每页50行共1000行的书来算,五百万行要有100卷,要用一整个书架子来摆置,这还仅仅是内核部分。用户程序,如GUI,库以及基本应用软件(如windows Explorer等),很容易就能达到这个数量的10倍或者20倍之多。

# 长寿:操作系统很难编写,如此大的代码量,一旦完成,操作系统所有者便不会轻易扔掉,再写一个。而是在原有的基础上进行改进。(基本上可以把windows95/98/Me看出一个操作系统,而windows NT/2000/XP/Vista则是两位一个操作系统,对于用户来说它们十分相似。还有UNIX以及它的变体和克隆版本也演化了多年,如System V版,Solaris以及FreeBSD等都是Unix的原始版,不过尽管linux非常依照UNIX模式而仿制,并且与UNIX高度兼容,但是linux具有全新的代码基础)

操作系统发展史

第一代(1940~1955) 手工操作----穿孔卡片

第一代计算机的产生背景: 第一代之前人类是想用机械取代人力,第一代计算机的产生是计算机由机械时代进入电子时代的标志,从Babbage失败之后一直到第二次世界大战,数字计算机的建造几乎没有什么进展,第二次世界大战刺激了有关计算机研究的爆炸性进展。 lowa州立大学的john Atanasoff教授和他的学生Clifford Berry建造了据认为是第一台可工作的数字计算机。该机器使用300个真空管。大约在同时,Konrad Zuse在柏林用继电器构建了Z3计算机,英格兰布莱切利园的一个小组在1944年构建了Colossus,Howard Aiken在哈佛大学建造了Mark 1,宾夕法尼亚大学的William Mauchley和他的学生J.Presper Eckert建造了ENIAC。这些机器有的是二进制的,有的使用真空管,有的是可编程的,但都非常原始,设置需要花费数秒钟时间才能完成最简单的运算。 在这个时期,同一个小组里的工程师们,设计、建造、编程、操作及维护同一台机器,所有的程序设计是用纯粹的机器语言编写的,甚至更糟糕,需要通过成千上万根电缆接到插件板上连成电路来控制机器的基本功能。没有程序设计语言(汇编也没有),操作系统则是从来都没听说过。使用机器的过程更加原始,详见下‘工作过程’

# 特点:
# 没有操作系统的概念
# 所有的程序设计都是直接操控硬件

# 工作过程:
# 程序员在墙上的机时表预约一段时间,然后程序员拿着他的插件版到机房里,将自己的插件板街道计算机里,这几个小时内他独享整个计算机资源,后面的一批人都得等着(两万多个真空管经常会有被烧坏的情况出现)。

# 后来出现了穿孔卡片,可以将程序写在卡片上,然后读入机而不用插件板
# 优点:

# 程序员在申请的时间段内独享整个资源,可以即时地调试自己的程序(有bug可以立刻处理),但资源利用率低

# 缺点:
# 浪费计算机资源,一个时间段内只有一个人用。
# 注意:同一时刻只有一个程序在内存中,被cpu调用执行,比方说10个程序的执行,是串行的

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

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

批处理系统: 加载在计算机上的一个系统软件,在他控制下,计算机能够自动的批处理一个或多个用户的作业(这作业包括程序,数据和指令)

联机批处理系统

首先出现的是联机批处理系统,即作业的输入/输出由CPU来处理

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

脱机批处理系统

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

微型机: 一台不与主机直接相连而专门用于与输入/输出设备打交道的.其功能是:

# 从输入机上读取用户作业并放到输入磁带上

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

这样,主机不是直接与慢速的输入/输出设备打交道,而是与速度相对较快的磁带机发生关系,有效缓解了主机与设备的矛盾,主机与微型机可并行工作,两者分工明确,可以充分发挥主机的高速计算能力. 脱机批处理系统: 20世纪60年代应用十分广泛,极大的缓解了人机矛盾及主机与外设的矛盾. 不足: 每次主机内存仅存放一道作业,每当他运行期间发出输入/输出(I/O)请求后,高速的CPU便处于等待低俗的I/O完成状态,致使CPU空闲. 为改善CPU利用率,又引入了多到程序系统.

多道程序系统

多道程序设计技术

所谓多道程序设计技术,就是指多个程序同事进入内存并运行,即同时把多个程序放入内存,并允许他们交替在CPU中运行,他们共享系统中的各种硬、软件资源,当一道程序因I/O请求而暂停运行时,CPU便立即运行另一道程序.

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

将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,并交替运行.

多道程序系统的出现,标志着操作系统逐渐程序的阶段,先后出现了作业调度管理,处理机管理,存储器管理,外部设备管理,文件系统管理等功能. 由于多个程序同事在计算机中运行,开始有了空间隔离的概念,只有内存空间的隔离,才能让数据更加安全,稳定. 除了空间隔离之外,多道技术还第一次体现了时空复用的特点,遇到I/O操作就切换程序,使得CPU利用率提高了,计算机的工作效率也随之提高.

多道批处理系统

20世纪60年代中期,在前述的批处理系统中,引入多道程序设计技术后形成多道批处理系统(简称:批处理系统)。 它有两个特点: (1)多道:系统内可同时容纳多个作业。这些作业放在外存中,组成一个后备队列,系统按一定的调度原则每次从后备作业队列中选取一个或多个作业进入内存运行,运行作业结束、退出运行和后备作业进入运行均由系统自动实现,从而在系统中形成一个自动转接的、连续的作业流。 (2)成批:在系统运行过程中,不允许用户与其作业发生交互作用,即:作业一旦进入系统,用户就不能直接干预其作业的运行。 批处理系统的追求目标:提高系统资源利用率和系统吞吐量,以及作业流程的自动化。   批处理系统的一个重要缺点:不提供人机交互能力,给用户使用计算机带来不便。   虽然用户独占全机资源,并且直接控制程序的运行,可以随时了解程序运行情况。但这种工作方式因独占全机造成资源效率极低。   一种新的追求目标:既能保证计算机效率,又能方便用户使用计算机。 20世纪60年代中期,计算机技术和软件技术的发展使这种追求成为可能。

分时系统

由于CPU速度不断提高和采用分时技术,一台计算机可同时连多个用户终端,而每个用户可在自己的终端上联机使用计算机,好像自己独占计算机一样. 分时技术: 把处理机的运行时间分成很短的时间片,按时间片轮流把处理机分配给各联机作业使用. 若某个作业在分配给它的时间片内不能完成其计算,则该作业暂时中断,把处理机让给另一作业使用,等待下一轮时再继续其运行。由于计算机速度很快,作业运行轮转得很快,给每个用户的印象是,好象他独占了一台计算机。而每个用户可以通过自己的终端向系统发出各种操作控制命令,在充分的人机交互情况下,完成作业的运行。

具有上述特征的计算机系统成为分时系统,他允许多个用户同时联机使用计算机.

特点

# 1)多路性。若干个用户同时使用一台计算机。微观上看是各用户轮流使用计算机;宏观上看是各用户并行工作。  
# 2)交互性。用户可根据系统对请求的响应结果,进一步向系统提出新的请求。这种能使用户与系统进行人机对话的工作方式,明显地有别于批处理系统,因而,分时系统又被称为交互式系统。  
# 3)独立性。用户之间可以相互独立操作,互不干扰。系统保证各用户程序运行的完整性,不会发生相互混淆或破坏现象。  
# 4)及时性。系统可对用户的输入及时作出响应。分时系统性能的主要指标之一是响应时间,它是指:从终端发出命令到系统予以应答所需的时间。

分时系统的主要目标: 对用户响应的及时性,即不至于用户等待每一个命令的处理过长. 分时系统可以同时接纳数十个甚至上百个用户,由于内存空间有限,往往采用对换(又称交换)方式的存储方法,即将未'轮到'的作业放入磁盘,一旦轮到,再将其调入内存;而时间片用完后,又将作业存回磁盘(俗称滚进,滚出法),使同一存储区域轮流为多个用户服务. 多用户分时系统是当今计算机操作系统最普遍使用的一类操作系统. 注意: 分时系统的分时间片工作,在没有遇到IO操作的时候就完成了自己的时间片被切走了,这样的切换工作其实并没有提高CPU的效率,反而使计算机的效率降低了,但是我们牺牲了一点效率,却实现了多个程序共同执行的效果,这样你就可以在计算机上一边听音乐一边聊QQ了.

实时系统

虽然多道批处理系统和分时系统能获得令人满意的资源利用率和系统响应时间,但却不能满足实时控制与实时信息处理两个应用领域的需求,于是产生了实时系统,即系统能够及时响应随机发生的外部事件,并在严格的时间范围内完成对事件的处理.

实时系统可分为两类:

  1. 实时控制系统. 当用于飞机飞行、导弹发射等的自动控制时,要求计算机能尽快处理测量系统测得的数据,及时地对飞机或导弹进行控制,或将有关信息通过显示终端提供给决策人员。当用于轧钢、石化等工业生产过程控制时,也要求计算机能及时处理由各类传感器送来的数据,然后控制相应的执行机构。
  1. 实时信息处理系统。当用于预定飞机票、查询有关航班、航线、票价等事宜时,或当用于银行系统、情报检索系统时,都要求计算机能对终端设备发来的服务请求及时予以正确的回答。此类对响应及时性的要求稍弱于第一类。

实时操作系统的主要特点: 1 . 及时响应. 每一个信息接收、分析处理和发送的过程必须在严格的时间限制内完成. 2 . 高可靠性. 需采取冗余措施,双机系统前后台工作,也包括必要的保密措施. 分时—— 现在流行的PC,服务器都是采用这种运行模式,即把CPU的运行分成若干时间片分别处理不同的运算请求linux系统. 实时—— 一般用于单片机、PLC等,比如电梯的上下控制中,对于按键等动作要求进行实时处理.

通用操作系统

操作系统的三种基本类型: 多道处理系统、分时系统、实时系统. 通用操作系统: 具有多种类型特征的操作系统,可以同时兼有多道批处理、分时、实时处理的功能,或其中两种以上的功能. 例如: 实时处理+批处理=实时批处理系统,首先保证优先处理实时任务,插空进行批处理作业,常把实时任务作为前台作业,批作业称为后台作业. 再如: 分时处理+批处理=分时批处理系统.即:时间要求不强的作业放入“后台”(批处理)处理,需频繁交互的作业在“前台”(分时)处理,处理机优先运行“前台”作业。 从上世纪60年代中期,国际上开始研制一些大型的通用操作系统。这些系统试图达到功能齐全、可适应各种应用范围和操作方式变化多端的环境的目标。但是,这些系统过于复杂和庞大,不仅付出了巨大的代价,且在解决其可靠性、可维护性和可理解性方面都遇到很大的困难。 相比之下,UNIX操作系统却是一个例外。这是一个通用的多用户分时交互型的操作系统。它首先建立的是一个精干的核心,而其功能却足以与许多大型的操作系统相媲美,在核心层以外,可以支持庞大的软件系统。它很快得到应用和推广,并不断完善,对现代操作系统有着重大的影响。 至此,操作系统的基本概念、功能、基本结构和组成已形成并渐趋完善.

操作系统的进一步发展

进入20世纪80年代,大规模继承电路工艺的飞跃发展,微处理机的出现喝发展,掀起了计算机大发展大普及的浪潮,一方面迎来了个人计算机的时代,同时又向计算机网络、分布式处理、巨型计算机和智能化方向的发展,于是操作系统有了进一步的发展,如: 个人计算机操作系统、网络操作系统、分布式操作系统等。

至此,操作系统的基本概念、功能、基本结构和组成都已形成并渐趋完善.

个人计算机操作系统

个人计算机上的操作系统是联机交互的单用户操作系统,他提供的联机交互功能与通用分时系统提供的功能很相似. 由于是个人专用,因此一些功能简单的多,然而,由于个人计算机的应用普及,对于提供方便友好的用户接口和丰富的文件系统要求会愈来愈迫切.

网络操作系统

计算机网络: 通过通信设施,将地理上分散的,具有自治功能的多个计算机互联起来,实现信息交换,资源共享,互操作和协作处理的系统. 网络操作系统: 在原来各自计算机操作系统上,按照网络体系结构的各个协议标准增加网络管理模块,其中包括: 通信,资源共享,系统安全和各种网络应用服务。

分布式操作系统

表面上看,分布式系统与计算机网络没有多大区别,分布式操作系统也是通过通信网络,将地理上分散的具有自治功能的数据处理系统或计算机系统互连起来,实现信息交换和资源共享,写作完成任务. ——硬件连接相同. 但是如下有一些明显的区别:

  1. 分布式系统要求一个统一的操作系统,实现系统操作的统一性.
  2. 分布式操作系统管理分布式系统中的所有资源,他负责全系统的资源分配和调度、任务划分、信息传输和控制协调工作,并为用户提供一个统一的界面.
  3. 用户通过这一界面,实现所需要的操作和使用系统资源,至于操作定在那一台计算机上执行,或使用那台计算机资源,则是操作系统完成,用户不知道,此为: 系统的透明性.
  4. 分布式系统更强调分布式计算和处理,因此对于多机合作和系统重构、坚强性和容错能力有更高的要求,希望系统有: 更短的响应时间、高吞吐量和高可靠性.

操作系统的作用

现代的计算机系统主要由一个或者多个处理器,主存,硬盘,键盘,鼠标,显示器,打印机,网络接口及其他输入输出设备组成.

一般而言,现代计算机系统是一个复杂的系统. 其一: 如果每位应用程序员都必须掌握该系统所有的鞋机,那就不可能再编写代码了(严重影响了程序员的开发效率;全部掌握这些细节可能需要一万年.) 其二: 并且管理这些部件加以优化使用,是一件极富挑战性的工作,于是计算机安装了一层软件(系统软件),成为操作系统,他的任务就是为用户程序提供一个更好、更简单的、更清晰的计算机模型,并管理刚才提及的所有设备.

操作系统原理

资源管理解决物理资源数量不足和合理分配资源两个问题

​ 操作系统虚拟机为用户提供了一种简单、清晰、易用、高效的计算机模型。虚拟机的每种资源都是物力资源通过复用、虚拟和抽象而得到的产物。 ​ 虚拟机提供进程运行的逻辑计算环境。从概念上来说,一个进程运行在一台虚拟机上,可以认为一个进程就是一台虚拟机,一台虚拟机就是一个进程。 复用:空分复用共享时分复用共享。 a. 空分复用共享(space-multiplexed sharing): 将资源从“空间”上分割成更小的单位供不同进程使用。在计算机系统中,内存和外存(磁盘)等是空分复用共享的。 b. 时分复用共享(time-multiplexed sharing): 将资源从“时间”上分割成更小的单位供不同进程使用。在计算机系统中,处理器和磁盘机等是时分复用共享的。 虚拟:对资源进行转化、模拟或整合,把一个物理资源转变成多个逻辑上的对应物,也可以把多个物理资源变成单个逻辑上的对应物,即创建无须共享独占资源的假象,或创建易用且多于实际物理资源的虚拟资源假象,以达到多用户共享一套计算机物理资源的目的。虚拟技术可用于外部设备(外部设备同时联机操作(SPOOLing)),存储资源(虚拟内存)和文件系统(虚拟文件系统(Virtual File System, VFS))中。 复用和虚拟相比较,复用所分割的是实际存在的物理资源,而虚拟则实现假想的同类资源。虚拟技术解决某类物理资源不足的问题,提供易用的虚拟资源和更好的运行环境。 抽象:通过创建软件来屏蔽硬件资源的物理特性和实现细节,简化对硬件资源的操作、控制和使用。 复用和虚拟的主要目标是解决物理资源数量不足的问题,抽象则用于处理系统复杂性,重点解决资源易用性。

系统调用

系统调用: 为给应用程序的运行提供良好环境,内核提供了一系列具有预定功能的服务例程,通过一组称为系统调用(System Call)的接口呈现给用户,系统调用把应用程序的请求传送至内核,调用相应的服务例程完成所需处理,将处理结果返回给应用程序。

注:系统调用的编号称为功能号

系统调用的执行过程: 当CPU执行程序中编写的由访管指令(supervisor, 也称自陷指令(trap)或中断指令(interrupt), 指引起处理器中断的机器指令)实现的系统调用时会产生异常信号,通过陷阱机制(也称异常处理机制,当异常或中断发生时,处理器捕捉到一个执行线程,并且将控制权转移到操作系统中某一个固定地址的机制),处理器的状态由用户态(user mode, 又称目态或普通态)转变为核心态(kerbel mode, 又称管态或内核态),进入操作系统并执行相应服务例程,以获得操作系统服务。当系统调用执行完毕时,处理器再次切换状态,控制返回至发出系统调用的程序。 系统调用是应用程序获得操作系统服务的唯一途径。

系统调用的作用

# 1. 内核可以基于权限和规则对资源访问进行裁决,保证系统的安全性。 
# 2. 系统调用对资源进行抽象,提供一致性接口,避免用户在使用资源时发生错误,且编程效率大大提高。

# 系统调用与函数调用的区别: 
# 1. 调用形式和实现方式不同。功能号 VS 地址; 用户态转换到内核态 VS 用户态。 
# 2. 被调用代码的位置不同。 动态调用 + 操作系统 VS 静态调用 + 用户级程序。 
# 3. 提供方式不同。 操作系统 VS 编程语言。
操作系统内核

内核: 是一组程序模块,作为可信软件来提供支持进程并发执行的基本功能和基本操作,通常驻留在内核空间,运行于内核态,具有直接访问硬件设备和所有内存空间的权限,是仅有的能够执行特权指令的程序。 内核的功能: a. 中断处理。中断处理是内核中最基本的功能,也是操作系统赖以活动的基础。 b. 时钟管理。时钟管理是内核的基本功能。 c. 短程调度。短程调度的职责是分配处理器,按照一定的策略管理处理器的转让,以及完成保护和恢复现场工作。 d. 原语管理。 原语是内核中实现特定功能的不可中断过程。 内核是操作系统对裸机的第一次改造,内核和裸机组成了第一层虚拟机,进程在虚拟机上运行。

处理器状态: 内核态和用户态

仅在内核态下才能使用的指令称为特权指令,执行这些指令不仅影响运行程序自身,而且还会干扰其他程序及操作系统。 非特权指令在内核态和和用户态下都能工作。 现代计算机为处理器建立硬件标志位,称处理器状态位,通常是程序状态字(Program Status Word, PSW)中的一位,来将处理器的状态设置为内核态或用户态。 用户态向内核态转换的情况: a. 程序请求操作系统服务, 执行系统调用。 b. 在程序运行时产生中断事件(如I/O操作完成),运行程序被中断,转向中断处理程序处理。 c. 在程序运行时产生异常事件(如在目态下执行特权指令),运行程序被打断,转向异常处理程序工作。 以上三种情况都是通过中断机制发生,可以说中断和异常是用户态到内核态转换的仅有途径。 用户栈和核心栈 a. 用户栈是用户进程空间中的一块区域。用于保存应用程序的子程序(函数)间相互调用的参数,返回值,返回点和子程序的局部变量。 b. 核心栈是内存中操作系统空间的一块区域。用于保存中断现场和保存操作系统程序(函数)间相互调用的参数,返回值,返回点和程序的局部变量。

中断

中断:程序执行过程中遇到急需处理的事件时,暂时终止现行程序在CPU上的运行,转而执行相应的事件处理程序,待处理完成后再返回断点或调度其他程序的执行过程。 中断的分类: a. 外中断(又称中断或异步中断): 来自处理器之外的中断信号,如,时钟中断、键盘中断等。外中断可分为可屏蔽中断和非可屏蔽中断。 b. 内中断(又称异常或同步中断),来自处理器内部的中断信号,如,访管中断,硬件故障中断,程序性中断等。内中断不能被屏蔽。 中断和异常的响应: 发现中断源 → 保护现场 → 转向中断/异常事件处理程序执行 → 恢复现场

进程

进程:具有独立功能的程序在某个数据集合上的一次运行活动,也是操作系统进行资源分配和保护的基本单位。 a. 从原理角度看,进程是支持程序执行的一种系统机制,它对处理器上运行程序的活动进行抽象。 b. 从实现角度看,进程是一种数据结构,用来准确地刻画运行程序的状态和系统动态变化状况 进程状态的七态模型

# a. 新建态(new): 进程被创建,尚未进入就绪队列。 
# b. 就绪态(ready): 进程具备运行条件,等待系统分配处理器。 
# c. 挂起就绪态(ready suspend):进程具备运行条件,但目前在外存中。 
# d. 运行态(running): 进程占有处理器正在运行。 
# e. 终止态(exit): 进程达到正常结束点或被其他原因所终止,下一步将被撤销。 
# f. 等待态(wait): 又称阻塞态或休眠态。进程正在等待某个事件完成,目前不具备运行条件。 
# g. 挂起等待态(blocked suspend): 进程正在等待某个事件完成,并且在外存中。

# 程序和数据刻画进程的静态特征,称为进程控制块的一种数据结构刻画进程的动态特征。进程映像(process image)包括进程控制块、进程程序块、进程核心块、进程数据块等要素。

# 进程控制块(Process Control Block, PCB):进程存在的唯一标识,操作系统掌握进程的唯一资料结构和管理进程的主要依据。包括标识信息、现场信息和控制信息等信息。

# 进程队列(process queue):处于同一状态的所有进程的PCB链接在一起的数据结构。 有两种队列组织方式:链接方式和索引方式。

# 进程切换必定在内核态而非用户态发生。

# 进程可以分为两部分,资源集合和线程集合。进程要支撑线程运行,为线程提供虚拟地址空间和各种资源。进程封装管理信息,线程封装执行信息
处理器调度

处理器调度层次:

a. 高级调度: 又称作业调度、长程调度。从输入系统的一批作业(job, 用户提交给操作系统计算的一个独立任务)中按照预定的调度策略挑选若干作业进入内存,为其分配所需资源并创建对应作业的用户进程。 b. 中级调度: 又称平衡调度,中程调度。根据内存资源情况决定内存所能容纳的进程数目,并完成外存和内存中进程对换工作。 c. 低级调度:又称进程调度/线程调度,短程调度。根据某种原则决定就绪队列中那个进程/线程先获得处理器,并将处理器出让给它使用。

低级调度算法

a. 先来先服务(First Come First Server, FCFS)算法。 b. 最短作业优先(Shortest Job First, SJF)算法。 c. 最短剩余时间优先(Shortest Remaining Time First, SRTF)算法: 假设当前某进程/线程正在运行,如果有新进程/线程移入就绪队列,若它所需的CPU运行时间比当前运行的进程/线程所需的剩余CPU时间还短,抢占式最短作业优先算法强行剥夺当前执行者的控制权,调度新进程/线程执行。 d. 最高响应比优先(Highest Response Ratio First, HRRF)算法:非剥夺式算法。其中,响应比 = (作业已等待时间 + 作业处理时间) / 作业处理时间。 e. 优先级调度算法:优先级高的选择进程/线程优先选择。 f. 轮转调度(Round-Robin, RR)算法: 也称时间片调度。就绪队列的进程轮流运行一个时间片。 g. 多级反馈队列(Multi-Level Feedback Queue, MLFQ)算法。

# 衡量调度算法的性能指标: 
# a. 资源利用率: CPU利用率 = CPU有效工作时间/(CPU有效工作时间 + CPU空闲等待时间) 
# b. 吞吐率: 单位时间内CPU处理作业的个数。 
# c. 公平性: 确保每个进程都能获得合理的CPU份额和其他资源份额,不会出现饥饿现象。 
# d. 响应时间: 从交互式进程提交一个请求(命令)直到获得响应之间的时间间隔。 
# e. 周转时间: 批处理用户从向系统提交作业开始到作业完成为止的时间间隔。 
# 平均周转时间:T = (∑ni=1ti
进程的交互
# 进程互斥(Mutual Exclusion): 若干进程因相互抢夺独占型资源而产生的竞争制约关系。

# 进程同步(Synchronization): 为完成共同任务的并发进程基于某个条件来协调其活动,因为需要在某些位置上排定执行的先后次序而等待、传递信息或消息所产生的协作制约关系。

# 资源竞争会引发两个控制问题: 
# a. 死锁: 一组进程因争夺资源陷入永远等待的状态。 
# b. 饥饿: 一个可运行进程由于由于其他进程总是优先于它,而被调度程序无限期地拖延而不能被执行。
临界区管理

并发进程中与共享变量有关的程序段称为临界区(Critical Section)。共享变量所代表的资源称为临界资源(Critical Resource),即一次仅能供一个进程使用的资源。 临界区调度原则: a. 择一而入。 一次之多只有一个进程进入临界区内执行。 b. 忙则要等。 如果已有进程在临界区中, 试图进入此临界区的其他进程应等待。 c. 有限等待。 进入临界区内的进程应在有限时间内退出。 临界区管理的软件算法:Peterson算法。 为每个进程设置标志,当标志值为 true 时表示该进程要求进入临界区,另外再设置一个指示器 turn 以指示可以由哪个进程进入临界区,当 turn = i 时则可由 Pi 进入临界区。

 1 /* Peterson 算法 */
 2 
 3 bool inside[2];
 4 inside[0] = false;
 5 inside[1] = false;
 6 enum { 0, 1 } turn;
 7 
 8 /* 进程0 */
 9 process P0(){
10     inside[0] = true;               //请求...
11     turn = 1;
12     while(inside[1] && turn == 1) ; //等待...
13 
14     /*临界区 */
15 
16     inside[0] = false;              //归还...
17 }
18 
19 /* 进程1 */
20 process P1(){
21     inside[1] = true;               //请求...
22     turn = 0;
23     while(inside[0] && turn == 0) ; //等待...
24 
25     /*临界区 */
26 
27     inside[1] = false;              //归还...
28 }

Peterson算法满足临界区管理的三个原则。 临界区管理的硬件设施: a. 关中断。 在进程进入临界区时关中断,进程退出临界区时开中断。 b. 测试并设置指令。 利用机器指令TS(Test and Set)实现临界区的上锁和开锁原语操作。 c. 对换指令。 利用对换指令实现临界区的上锁和开锁原语操作。

信号量(samaphore)和PV操作

PV操作都是原语操作, 不可中断。

信号量和PV操作

 1 // 信号量
 2 typedef struct semaphore {
 3     int value;                 // 信号量值
 4     struct pcb* list;          // 指向“等待该信号量的进程队列”的指针
 5 };  
 6 
 7 // P操作
 8 void P(semaphore s){
 9     s.value--;                 // 信号量值减一
10 
11     // 如果信号量值小于0, 执行P操作的进程调用sleep(s.list)阻塞自己,
12     // 被置成“等待信号量s”状态,并移入s信号量队列,转向进程调度程序。
13     if(s.value < 0) sleep(s.list);
14 }
15 
16 // V操作
17 void V(semaphore s){
18     s.value++;                 // 信号量值加一
19 
20     // 如果信号量小于等于0, 则调用wakeup(s.list)释放一个等待信号量s的进程,
21     // 并转换成就绪态, 进程则继续执行。
22     if(s.value <= 0) wakeup(s.list);
23 }

a. 若信号量值 s.value 为正值, 此值等于在封锁进程之前对信号量 s 可施行P操作的次数,即,s所代表的实际可用的资源数。 b. 若信号量值 s.value 为负值, 其绝对值等于登记在 s 信号量队列中的等待进程的数目。 c. 通常P操作意味着请求一个资源,V操作意味着释放一个资源。在一定条件下,P操作也可表示挂起进程的操作,V操作代表唤醒被挂起进程的操作。

信号量实现互斥

 1 semaphore mutex;
 2 mutex = 1;
 3 
 4 //进程Pi, i = 1, 2 ..., n
 5 process Pi(){
 6     P(mutex);
 7 
 8     /* 临界区 */
 9 
10     V(mutex);
11 }
管程

管程(monitor):代表共享资源的数据结构及并发进程在其上执行的一组构成就构成管程,管程被请求和释放资源的进程锁调用。

a. 条件变量。 管程内的一种数据结构。只有在管程中才能被访问,进程可以在条件变量上等待或被唤醒。只能通过 wait()signal() 原语操作来控制。 b. wait() 原语。 挂起调用进程并释放管程,直至另一个进程在条件变量上执行 signal()。 c. signal() 原语。如果有其他的进程因对条件变量执行 wait() 而被挂起,便释放之。 如果没有进程在等待,那么相当于空操作,信号不被保存。

死锁

死锁的主要解决方法: 死锁防止、死锁避免、死锁检测和恢复。 死锁产生的必要条件: a. 互斥条件。 临界资源是独占资源,进程应互斥且排他地使用这些资源。 b. 占有和等待条件。 进程在请求资源得不到满足而等待时,不释放已占有的资源。 c. 不剥夺条件。已获资源只能由进程资源释放,不允许被其他程序剥夺。 d. 循环等待条件。 存在循环等待链,其中每个进程都在等待下一个进程所持有的资源。

死锁的防止就是去破坏死锁产生的必要条件。 如,使资源可同时使用(破坏互斥条件)、静态分配资源(破坏占有和等待条件)、剥夺调度(破坏不剥夺条件)、层次分配策略(循环等待条件)等。 死锁避免: 银行家算法 (额…自己百度去吧。 = =!) 死锁检测和恢复: 进程-资源分配图(额…还是去百度吧。) a. 如果进程-资源分配图中无环路,此时系统没有死锁。 b. 如果进程-资源分配图中有环路,且每个资源类中只有一个资源,则系统发生死锁。 c. 如果进程-资源分配图中有环路,且所涉及的资源类有多个资源,则不一定会发生死锁。

可变分区存储管理

可变分区存储分配算法: a. 最先适应分配算法。从未分配区的开始位置开始扫描,在找到的第一个能满足长度要求的空闲区上分配存储空间。 b. 下次适应分配算法。从未分配区上次扫描结束处开始顺序查找,在找到的第一个能满足长度要求的空闲区上分配存储空间。 c. 最优适应分配算法。扫描整个未分配区,选择能满足用户进程要求的最小分区分配存储空间。 d. 最坏适应分配算法。扫描整个未分配区,选择能满足用户进程要求的最大分区分配存储空间。 e. 快速适应分配算法。为经常用到的长度的空闲区设立单独的空闲区链表。

分页存储管理

基本概念: a. 页面。 进程逻辑地址空间分成大小相等的区,每个区称为页面或页。(注: 页面的本质是逻辑地址空间) b. 页框(kuàng, 0.0)。 又称页帧。内存物理地址空间分成大小相等的区,其大小和页面大小相等,每个区就是一个页框。(注: 页框的本质是物理地址空间) c. 逻辑地址。分页存储器的逻辑地址由页号和页内偏移两部分组成。

d. 内存页框表。页框表的表项给出物理块使用情况:0为空闲,1为占用。 e. 页表。页表是操作系统为进程建立的,是程序页面和内存页框的对照表,页表的每一栏指明程序中的某一页面和分得的页框之间的关系。

分页存储管理的地址转换

翻译快表:也称转换后援缓冲(Translation Look_aside Buffer, TLB)。用来存放进程最近访问的部分页表项。(注: 翻译快表之于页表类似于Cache之于存储器) 二级页表:把整个页表分割成许多小页表,每个称为页表页,每个页表页含有若干个页表表项。页表页允许分散对应不连续的页框。为了找到页表页,应建立地址索引,称为页目录表,其表项指出页表页起始地址。 二级页表实现逻辑地址到物理地址转换的过程: 由硬件“页目录表基址寄存器”指出当前运行进程的页目录表的内存起始地址,加上“页目录位移”作为索引,可找到页表页在内存的起始地址,再以“页目录位移”作为索引,找到页表页在内存的起始位置,再以“页表页位移”作为索引,找到页表页的表项,此表项中包含一个页面对应的页框号,由页框号和页内偏移便可生成物理地址。

注: 类比于书的目录,找某一段内容的时候,先在目录上找到对应的章节,再在对应的章节下面找具体的知识点。比如,我要在《操作系统原理》中查“多级页表”。首先我知道它是在存储管理一章的,于是就找到了“第四章 存储管理”(类似于找到了页目录表)。 然后在第四章下面找“多级页表”(类似于在页目录表下面找具体的页表页)。最后找到“多级页表”对应的页码(类似于在页表页中找到其对应的页框)。最后查阅对应的章节页码(类似于读取对应页框的数据)。

分段存储管理

分段和分页的比较: a. 分段是信息的逻辑单位,由源程序的逻辑结构及含义所决定,是用户可见的,段长由用户根据需要来确定,段起始地址可以从任何内存地址开始。引入的目的是满足用户模块化程序设计的需要。 b. 分页是信息的物理单位,与源程序的逻辑无关,是用户不可见的,页长由系统(硬件)决定,页面只能从页大小的整数倍地址开始。引入目的是实现离散分配并提高内存利用率

虚拟存储管理

虚拟存储管理的基本思路:

把磁盘空间当做内存的一部分,进程的程序和数据部分放在内存中,部分放在磁盘上。程序运行时,它执行的指令或访问的数据在哪里由存储管理负责判断,并针对情况采取响应的措施。 请求分页虚存管理: 将进程信息副本存放在外存中,当它被调度投入运行时,程序和数据没有全部装进内存,仅装入当前使用页面,进程执行过程中访问到不在内存的页面时,产生缺页异常,再由系统自动调入。 全局页面替换策略(页面替换算法的作用范围是整个系统,不考虑进程的属主): a. 最佳页面替换算法(Optimal Replacement, OPT)。 淘汰不再访问的页或者距现在最长时间后才访问的页。 b. 先进先出页面替换算法(First in First Out Replacement, FIFO)。淘汰在内存中驻留时间最长的页。 c. 最近最少使用页面替换算法(Least Recently Used Replacement, LRU)。 淘汰最近一段时间内最久未被使用的页面。 d. 第二次机会页面替换算法(Second Chance Replacement, SCR)。 首先检查FIFO页面队列中的队首,这是最早进入内存的页面,如果其“引用位”为0,那么它最早进入且未被引用,此页被淘汰。如果其“引用位”为1,说明虽然它最早进内存,但最近仍在使用,于是将“引用位”清零,并把这个页面移到队尾,把它看做新调入的页面,再给它一次机会。 e. 时钟页面替换算法(Clock Policy Replacement, Clock)。与SCR算法思路一致。只是用循环队列来构造页面队列,队列指针指向可能被淘汰的页面。如果队列指针指向的页的“引用位”为1,则将其置为0,同时队列指针指向下一个页。 局部页面替换算法(页面替换算法的作用局限于进程自身,要为进程维护称为工作集的一组页面): a. 局部最佳页面替换算法(Local Minimum Replacement, MIN)。 在t时刻时,若页面P在未来(t, t+delta)时间段内未被引用,则它被淘汰。 b. 工作集置换算法。 在t时刻时,若页面P在未来(t-delta, t)时间段内未被引用,则它被淘汰。 c. 模拟工作集替换算法。 d. 缺页频率替换算法。

请求段页式虚拟内存管理
# 虚地址以程序的逻辑结构划分为段。

# 实地址划分为位置固定、大小相等的页框(块)。
# 逻辑地址分为三个部分:段号s、段内页号p、页内位移d。对于用户而言,段式虚拟地址应该由段号s和段内位移d’组成,操作系统内部自动把d’解释成段内页号p和页内位移号d。
I/O硬件原理:I/O控制方式

轮询方式: 又称程序直接控制方式。使用查询指令测试设备控制器的忙闲状态位,确定内存和设备是否能能交换数据。(注:所谓轮询,就好比,老湿依次问每一个童鞋:“有问题没?”, 如果没问题,就继续问下一个童鞋。如果这个童鞋有问题,这个老湿就停下了解决这个问题。然后又继续询问下一个童鞋。) 中断方式: 要求CPU和设备控制器及设备之间存在中断请求线,设备控制器的状态寄存器有相应的中断允许位。 a. 进程发出启动I/O指令。 b. 设备控制器检查状态寄存器的内容,执行相应的I/O操作,一旦传输完成,设备控制器通过中断请求线发出I/O中断信号。 c. CPU收到并响应I/O中断后,转向设备的I/O中断处理程序执行。 d. 中断处理程序执行数据读取操作,将I/O缓冲寄存器的内容写入内存。操作结束后退出中断程序恢复之前的状态。 e. 执行中断前之前运行的进程。 (注: 类似于老湿在上面讲课,有童鞋问问题时,老湿就记录下自己讲到的位置,然后取回答童鞋的问题,回答完之后,又回到刚刚讲课的地方继续讲课) DMA(Direct Memory Access, 直接存储器存取)方式: 内存和设备之间有一条数据通路成块的传输数据,无须CPU干9预,实际数据传输操作由DMA直接完成。 通道方式: CPU在执行主程序时遇到I/O请求,启动在指定通道上选址的设备,一旦启动成功,通道开始控制设备进行操作,这时CPU就可以执行其他任务并与通道并行工作,直到I/O操作完成;当通道发出I/O操作结束中断时,处理器才响应并停止当前工作,转而处理I/O操作结束时间。

I/O软件原理

I/O中断处理程序: 通常是设备驱动程序的组成部分之一。检查设备状态寄存器内容,判断产生中断原因,根据I/O操作的完成情况进行相应处理。若数据传输有错,应向上层软件报告设备出错信息,实施重新执行;若正常结束,应唤醒等待传输的进程,使其转换为就绪态;若有等待传输的I/O命令,应通知相关软件启动下一个I/O请求。 I/O设备驱动程序:设备驱动程序是设备专有的。把用户提交的逻辑I/O请求转化为物理I/O的启动和执行。同时监督设备是否正确执行,管理数据缓冲区,进行必要的纠错处理。 独立于设备的I/O软件 用户空间的I/O软件

缓冲技术

缓冲技术的基本思想: 当进程执行写操作输出数据时,先向系统申请一个输出缓冲区,然后将数据送至缓冲区,若是顺序写请求,则不断地把数据填入缓冲区,直至装满为止,此后进程可以继续计算,同时,系统将缓冲区的内容写在设备上。当进程执行读操作输入数据时,先向系统申请一个输入缓冲区,系统将设备上的一条物理记录读至缓冲区,根据要求把当前所需要的逻辑记录从缓冲区中选出并传送给进程。 单缓冲: 是最简单的缓冲技术,每当有I/O请求时,操作系统就在内存的系统区中开设一个缓冲区。不允许多个进程同时对一个缓冲器操作。

双缓冲: CPU可把输出到设备的数据放入其中一个缓冲器(区)、让设备慢慢处理;然后,它又可以从另一个为终端设置的缓冲器(区)中读取所需要的输入数据。

  • 多缓冲: 是把多个缓冲区连接起来组成两部分,一部分专门用于输入,另一部分专门用于输出的缓冲结构。
驱动调度技术

磁盘的物理结构

磁盘包括多个盘面,每个盘面有一个读写磁头,所有的磁头都固定在唯一的移动臂上同时移动。一个盘面上的读写磁头的轨迹称为磁道,读写磁头下的所有磁道形成柱面,一个磁道又可以划分为多个扇区。在磁盘上定位某个物理记录需要知道其柱面号、磁头号以及扇区号。定位物理记录时,磁头到达指定扇区的时间称为查找时间, 选择磁头号并旋转至指定扇区的时间称为搜索延迟。

  • 磁道(柱面)的搜索定位算法: a. 先来先服务算法(First Come First Server algorithm, FCFS)。 b. 最短查找时间优先算法: 总是执行查找时间最短的请求。 c. 扫描算法: 移动臂来回的扫过所有柱面,扫描处遇到I/O请求便进行处理。 d. 分步扫描算法: 将I/O请求分为长度为N的子队列,按FIFO算法依次处理每个队列,而每个子队列采用扫描算法,处理完一个后再服务下一个队列。 e. 电梯调度算法: 又称LOOK算法。

磁头号由外向内递增。 f. 循环扫描算法: 移动臂总是从0号柱面至最大号柱面顺序扫描,然后直接返回0号柱面重复进行,归途中不提供服务(而扫描算法归途是要提供服务的)。

设备独立性
  • 设备独立性: 用户通常不指定物理设备,而是指定逻辑设备,使得用户作业和物理设备分离开来,再通过其他途径建立逻辑设备和物理设备之间的映射,设备的这种特性称为设备独立性。
虚拟设备
  • 外部设备同时联机操作(Simultaneous Peripheral Operations On Line, SPPPLing):

a. 预输入程序。 控制信息从输入设备至输入井,填写预输入表以便在作业执行过程中要求输入信息时可以随时找到其存放位置。 b. 井管理程序。 作业执行过程中要求启动某台设备进程I/O操作时,作业控制程序截获这个要求并调用井管理程序控制从相应输入井读取信息,或将信息送至输出井。 c. 缓输出程序。 当处理器空闲时,操作系统调用缓输出程序执行缓输出,它查看缓输出表是否有输出打印的文件,文件打印前还可能组织作业或文件标题,也可能对从输出井中读出的信息进行格式加工。

文件逻辑结构*
  • 文件的逻辑结构的两种形式: a. 流式文件。 一种无结构的文件,文件内的数据不再组成记录,只是一串顺序的信息集合,称为字节流文件。 b. 记录式文件。 一种有结构的文件,包含若干逻辑记录,逻辑记录是文件中按信息在逻辑上独立含义所划分的信息单位。
文件物理结构
  • 文件物理结构: 文件的物理结构和组织是指逻辑文件在物理存储空间中的存放方法和组织关系。
  • 常见组织方式:顺序文件、连接文件、直接文件和索引文件。

总结

什么是操作系统?操作系统在计算机系统中的主要作用是什么?
  • 定义: 操作系统尚无严格的定义。 一般可把操作系统定义为: 管理系统资源、控制程序执行、改善人机界面、提供各种服务,并合理组织计算机工作流程和为用户方便有效地使用计算机提供良好的运行环境的一种软件系统。
  • 作用: a. 服务用户。 操作系统作为用户接口和公共服务程序。 b. 进程交互。 操作系统作为进程执行的控制者和协调者。 c. 系统实现。 操作系统作为扩展机或虚拟机。 d. 资源管理。 操作系统作为资源的管理者和控制者。
什么是多道程序设计?多道程序设计有什么特点?
  • 多道程序设计(multiprogramming): 允许多个作业(程序)同时进入计算机系统的内存并启动交替计算的方法。
  • 多道程序设计的特点: 从宏观上看是并行的,从微观上看是串行的。
计算机操作系统为什么引入进程?
  • 刻画程序的并发性。
  • 解决资源的共享性。
在分时系统中,什么是响应时间?它与哪些因素有关?
  • 从交互式进程提交一个请求(命令)直到获得响应之间的时间间隔称为响应时间。
  • 影响分时操作系统的响应时间的因素很多,如,CPU的处理速度、联机终端的数目、所用时间片的大小、系统调度开销和对换信息量的多少等。
解释并发性与并行性
  • 计算机操作系统中把并行性和并发性明显区分开,主要是从微观的角度来说的,具体是指进程的并行性(多处理机的情况下,多个进程同时运行)和并发性(单处理机的情况下,多个进程在同一时间间隔运行的)。
  • 并行性是指硬件的并行性,两个或多个事件在同一时刻发生。
  • 并发性是指进程的并发性,两个或多个事件在同一时间段内发生。
试述存储管理的基本功能。
  • 存储分配。
  • 地址映射。
  • 存储保护。
  • 存储共享。
  • 存储扩充。
何谓地址转换(重定向)?哪些方法可以实现地址转换?
  • 地址重定位: 又称地址转换,地址映射。 可执行程序逻辑地址转换(绑定)为物理地址的过程。
  • 实现方法: a. 静态地址重定位。 由装载程序实现装载代码模块的加载和地址转换,把它装入分配给进程的内存指定区域,其中的所有逻辑地址修改成内存物理地址。 b. 动态地址重定位。

由装载程序实现装载代码模块的加载和地址转换,把它装入分配给进程的内存指定区域,但对链接程序处理过的应用程序的逻辑地址则不做任何修改,程序内存起始地址被置于硬件专用寄存器 —— 重定位寄存器。程序执行过程中,每当CPU引用内存地址(访问程序和数据)时,由硬件截取此逻辑地址,并在它被发送到内存之前加上重定位寄存器的值,以便实现地址转换。 c. 运行时链接地址重定位 程序链接的三种方式: a. 静态链接。在程序装载到内存和运行前,就已将它的所有目标模块及所需要的库函数进行链接和装配成一个完整的可执行程序且此后不可拆分。 b. 动态链接。在程序装入内存前并未事先进行程序各目标模块的链接,而是在程序装载时一边装载一边链接,生成一个可执行文件。 c. 运行时链接。 将某些目标模块或库函数的链接 推迟到执行时才进行。

什么是文件的共享?介绍文件共享的分类和实现思想。

文件共享: 不同进程共同使用同一个文件。

文件共享的分类: a. 静态共享。 两个或多个进程通过文件链接(一个文件同时属于多个目录,但实际上仅有一处物理存储)达到共享同一个文件的目的,无论进程是否运行,其文件的链接关系都是存在的,因此称为静态共享。 b. 动态共享。 系统不同的应用程序或同一用户的不同进程并发地访问同一文件,这种共享关系只有当进程存在时才可能出现,一旦进程消亡,其共享关系也就随之消失。 c. 符号链接共享文件共享: 不同进程共同使用同一个文件。

文件共享的分类: a. 静态共享。 两个或多个进程通过文件链接(一个文件同时属于多个目录,但实际上仅有一处物理存储)达到共享同一个文件的目的,无论进程是否运行,其文件的链接关系都是存在的,因此称为静态共享。 b. 动态共享。 系统不同的应用程序或同一用户的不同进程并发地访问同一文件,这种共享关系只有当进程存在时才可能出现,一旦进程消亡,其共享关系也就随之消失。 c. 符号链接共享

理论知识

# 一 操作系统的作用:
    # 1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口
    # 2:管理、调度进程,并且将多个进程对硬件的竞争变得有序

# 二 多道技术:
    # 1.产生背景:针对单核,实现并发
    # 现在的主机一般是多核,那么每个核都会利用多道技术
    # 有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,会被调度到4个
    # cpu中的任意一个,具体由操作系统调度算法决定。

    # 2.空间上的复用:如内存中同时有多道程序
    # 3.时间上的复用:复用一个cpu的时间片
    #   强调:遇到io切,占用cpu时间过长也切,核心在于切之前将进程的状态保存下来,这样
    #        才能保证下次切换回来时,能基于上次切走的位置继续运行

# 三 进程
	# 是计算机中最小的资源分配单位,每一个程序在运行起来的时候需要分配一些内存
    # 一个运行的程序
    # 在操作系统中用pid来唯一标识一个进程
    
# 四 线程
	# 是计算机中能CPU调度的最小单位,实际执行具体编译解释之后的代码的是线程,所以cpu执行的是解释之后线程中的代码
    
# 五 并行和并发
	# 并行  多个CPU各自在自己的CPU执行多个程序
    # 并发  一个CPU多个程序轮流执行

# 六 同步和异步
	# 同步  调用一个操作,要等待结果
    # 异步  调用一个操作不等待结束.
    
# 七 阻塞和非阻塞
	# 阻塞     CPU不工作
    # 非阻塞   CPU工作
    
# 	同步阻塞		调用一个函数要等待这个函数的执行结果,并且执行这个函数过程汇总CPU不工作 input,recv recvfrom
# 	同步非阻塞		调用一个函数要等待这个函数的执行结果,在执行这个函数的过程中CPU工作,ret = eval(1+2+3+4+5)

# 	异步非阻塞		调用一个函数不需要等待这个函数的执行结果,并且在执行这个函数的过程中CPU工作
# 	异步阻塞		调用一个函数不需要等待这个函数的执行结果,并且在执行这个函数的过程中CPU不工作
# 				    开启10个进程,异步的
# 				    获取这个进程的返回值,并且做到哪一个进程先结束,就先获取谁的返回值.


# 进程的三状态
# 就绪  运行 阻塞
# 点击运行 —> 操作系统接受指令分配一块空间给进程,创建对应的进程ID—>就绪 -> 运行(运行可能会遇到阻塞) -> 结束

# 进程的调度算法
	# 短作业和长作业有区别的, 越长的作业被调度的没有短作业调度的积极
    # 每一个IO操作都会让你辛苦排来的队执行的CPU机会让给其他程序.
	# 给所有的进程分配资源或者分配CPU使用权的一种方法
    # 短作业优先
    # 先来先服务
    # 多机反馈算法
    	# 多个任务队列,优先级从高到低
        # 新来的任务总是优先级最高的
        # 每一个新任务几乎会立即获得一个时间片时间
        # 执行完一个时间之后会降到一个时间片
        # 
        
# 进程开启和关闭
# 父进程  开启了子进程
# 父进程  要负责给子进程回收子进程结束之后的资源.

进程的相关介绍

什么是进程?

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。 广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

进程与程序的区别

程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。 而进程是程序在处理机的一次执行过程,他是一个动态的概念. 程序可以作为一种软件资料长期存在,而进程是有一定生命周期的。 程序是永久的,进程是暂时的.

注意: 同一个程序执行两次,就会在操作系统中出现两个进程,所以我们可以同时运行一个程序,分别做不通的事情也不会混乱.

进程的概念

第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。

第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。 进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。

操作系统引入进程的概念的原因:

从理论角度看,是对正在运行过程的抽象. 从实现角度看,是一种数据结构,目的在于清晰的刻画动态系统的内在规律,有效管理和调度进入计算机系统主要存储器运行的程序.

进程的特征:

动态性: 进程的实质是程序在多道程序中的一次执行过程,进程是动态产生,动态消亡的. 并发性: 任何进程都可以与其他进程一起并发执行. 独立性: 进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位. 异步性: 由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的,不可预知的速度向前推进. 结构特征: 进程由程序、数据和进程控制块三部分组成. 多个不同的进程可以包含相同的程序: 一个程序在不同数据集里构成不同的进程,能得到不同的结果,但是执行过程中,程序不能发生改变.

进程调度

要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随机运行的,而是要遵循一定的法则,由此就有了进程的调度算法.

先来先服务调度算法:

先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)。

短作业优先调度算法:

短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度。但其对长作业不利;不能保证紧迫性作业(进程)被及时处理;作业的长短只是被估算出来的。

时间片轮转法:

时间片轮转(Round Robin,RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,几十毫秒至几百毫秒。如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度。同时,进程调度程序又去调度当前就绪队列中的第一个进程。

显然,轮转法只能用来调度分配一些可以抢占的资源。这些可以抢占的资源可以随时被剥夺,而且可以将它们再分配给别的进程。CPU是可抢占资源的一种。但打印机等资源是不可抢占的。由于作业调度是对除了CPU之外的所有系统硬件资源的分配,其中包含有不可抢占资源,所以作业调度不使用轮转法。

在轮转法中,时间片长度的选取非常重要。首先,时间片长度的选择会直接影响到系统的开销和响应时间。如果时间片长度过短,则调度程序抢占处理机的次数增多。这将使进程上下文切换次数也大大增加,从而加重系统开销。反过来,如果时间片长度选择过长,例如,一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕,则轮转法变成了先来先服务法。时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的。

在轮转法中,加入到就绪队列的进程有3种情况: 一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行。 另一种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞。当阻塞解除之后再回到就绪队列。 第三种情况就是新创建进程进入就绪队列。 如果对这些进程区别对待,给予不同的优先级和时间片从直观上看,可以进一步改善系统服务质量和效率。例如,我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列,每个队列按FCFS原则排列,各队列之间的进程享有不同的优先级,但同一队列内优先级相同。这样,当一个进程在执行完它的时间片之后,或从睡眠中被唤醒以及被创建之后,将进入不同的就绪队列。

多级反馈队列:

前面介绍的各种用作进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式调度算法都将无法使用。

而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述。

(1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。

(2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。

(3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。

进程的并行与并发

并行: 并行是指两者同时执行,比如赛跑,两个人都在不停的往前跑:(资源够用,比如三个线程,四核的CPU)

并发: 并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完后继续给A,交替使用,目的是提高效率.

区别: (1) 并行是从微观上,也就是一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器. (2) 并发是从宏观上,在一个时间段上可以看出同时执行的,比如一个服务器同时处理多个session.

同步异步阻塞非阻塞

状态介绍:

  • 在了解其他概念之前,我们首先要了解进程的几个状态,在程序运行过程中,由于被操作系统的调度算法控制,程序会进入几个状态: 就绪,运行和阻塞.

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

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

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

同步和异步

所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才算完成,这是一种可靠的任务序列,要么成功都成功,失败都失败,两个任务的状态可以保持一致.

所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了,至于被依赖的任务是否真正完成,依赖他的任务也无法确定,所以他是不可靠的任务序列.

比如我去银行办理业务,可能会有两种方式: 第一种: 选择排队等候: 第二种: 选择取一个小纸条上面有我的号码,等到排到我这一号时由柜台的人通知我轮到我去办理业务了;

第一种: 前者(排队等候)就是同步等待消息通知,也就是我要等待银行办理业务情况. 第二种: 后者(等待别人通知)就是异步等待消息通知,在异步消息处理中,等待消息通知者(在这个例子中就是等待办理业务的人)往往注册一个回调机制,在所等待的事件被触发时由触发机制(在这里是柜台的人)通过某种机制(在这里是写在小纸条上的号码,喊号)找到等待该事件的人。

阻塞与非阻塞
  1. 同步阻塞形式 效率最低,拿上面的例子来说,就是你专心排队,什么别的事都不做.
  1. 异步阻塞形式 如果在银行等待办理业务的人采用的是异步的方式去等待消息被触发(通知),也就是领了一张小纸条,假如在这段时间他不能离开银行做其他的事情,那么很显然,这个人被阻塞了这个等待的操作上面. 异步操作是可以被阻塞住的,只不过他不是在处理消息时阻塞,而是在等待消息通知时被阻塞.
  1. 同步非阻塞形式 实际上是效率低下的 想象以下你一边打着电话一边还需要抬头看着底队伍排到你了没有,如果你打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在两种不同的行为之间来回切换,效率可想而知是低下的.
  1. 异步非阻塞形式 因为打电话(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换。 比如说,这个人突然发觉自己烟瘾犯了,需要出去抽根烟,于是他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下,那么他就没有被阻塞在这个等待的操作上面,自然这个就是异步+非阻塞的方式了。 很多人会把同步和阻塞混淆,是因为很多时候同步操作会以阻塞的形式表现出来,同样的,很多人也会把异步和非阻塞混淆,因为异步操作一般都不会在真正的IO操作处被阻塞。
进程的创建与结束

进程的创建

但凡是硬件,都需要操作系统去管理,只要有操作系统,就有进程的概念,就需要有创建进程的方式,一些操作系统只为一个应用程序设计,比如微波炉中的控制器,一旦启动微波炉,所有的进程都已存在.

而对于通用系统(跑很多应用程序),需要有系统运行过程中创建或撤销进程的能力,主要分为4中形式创建新的进程:

  1. 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)
  2. 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)
  3. 用户的交互式请求,而创建一个新进程(如用户双击暴风影音)
  4. 一个批处理作业的初始化(只在大型机的批处理系统中应用)

无论是哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的.

创建进程:

  1. 在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)
  2. 在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。

关于创建子进程,UNIX和windows

1.相同的是:进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。

  2.不同的是:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,从一开始父进程与子进程的地址空间就是不同的。

进程的结束

  1. 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)
  2. 出错退出(自愿,python a.py中a.py不存在)
  3. 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try…except…)
  4. 被其他进程杀死(非自愿,如kill -9)

在Python程序中的进程操作

之前我们已经了解了很多进程相关的理论知识,了解进程是什么应该不再困难了,刚刚我们已经了解了,运行中的程序就是一个进程。所有的进程都是通过它的父进程来创建的。因此,运行起来的python程序也是一个进程,那么我们也可以在程序中再创建进程。多个进程可以实现并发效果,也就是说,当我们的程序中存在多个进程的时候,在某些时候,就会让程序的执行速度变快。以我们之前所学的知识,并不能实现创建进程这个功能,所以我们就需要借助python中强大的模块。

multiprocess模块

仔细说来,multiprocess不是一个模块而是python中一个操作、管理进程的包。 之所以叫multi是取自multiple的多功能的意思,在这个包中几乎包含了和进程有关的所有子模块。由于提供的子模块非常多,为了方便大家归类记忆, 我将这部分大致分为四个部分:创建进程部分,进程同步部分,进程池部分,进程之间数据共享。

与线程不同,进程没有任何共享状态,

process模块介绍 process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。

Process([group [, target [, name [, args [, kwargs]]]]]),
# 由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)

# 强调:
# 1. 需要使用关键字的方式来指定参数
# 2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

# 参数介绍:
# group参数未使用,值始终为None
# target表示调用对象,即子进程要执行的任务
# args表示调用对象的位置参数元组,args=(1,2,'egon',)
# kwargs表示调用对象的字典,kwargs={'name':'egon','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字符的字符串。
# 这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)

# 在windows使用process模块的注意事项
在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动 import 启动它的这个文件,
# 而在 import 的时候又执行了整个文件。因此如果将process()直接写在文件中就会无限递归创建子进程报错。
# 所以必须把创建子进程的部分使用if __name__ ==‘__main__’ 判断保护起来,import 的时候  ,就不会递归运行了。`

使用process模块创建进程

在一个python进程中开启子进程,start方法和并发效果. 在python中启动的第一个子进程.

import time
from multiprocessing import Process

def f(name):
    print('hello',name)
    print('我是子进程')

if __name__ == '__main__':
    p = Process(target=f,args=('bob',))
    p.start()
    time.sleep(1)
    print('执行主进程的内容了')

Join方法

import time
from multiprocessing import Process

def f(name):
	print('hello',name)
	time.sleep(1)
	print('我是子进程')

if __name__ == '__main__':
	p = Process(target=f,args=('bob',))
	p.start()
	print('我是父进程')

查看主进程和子进程的进程号

import os
from multiprocessing import Process

def f(x):
    print('子进程id :',os.getpid(),'父进程id :',os.getppid())
    return x*x

if __name__ == '__main__':
    print('主进程id :', os.getpid())
    p_lst = []
    for i in range(5):
        p = Process(target=f, args=(i,))
        p.start()

多个进程同时执行

import time
from multiprocessing import Process

def f(name):
    print('hello', name)
    time.sleep(1)

if __name__ == '__main__':
    p_lst = []
    for i in range(5):
        p = Process(target=f, args=('bob',))
        p.start()
        p_lst.append(p)

多个进程同时运行,再谈join方法

import time
from multiprocessing import Process

def f(name):
    print('hello', name)
    time.sleep(1)

if __name__ == '__main__':
    p_lst = []
    for i in range(5):
        p = Process(target=f, args=('bob',))
        p.start()
        p_lst.append(p)
        p.join()
    # [p.join() for p in p_lst]
    print('父进程在执行')

多个进程同时运行,再谈join方法

import time
from multiprocessing import Process

def f(name):
    print('hello', name)
    time.sleep(1)

if __name__ == '__main__':
    p_lst = []
    for i in range(5):
        p = Process(target=f, args=('bob',))
        p.start()
        p_lst.append(p)
    # [p.join() for p in p_lst]
    print('父进程在执行')

除了上面这些开启进程的方法,还有一种以继承Process类的形式开启进程的方式

import os
from multiprocessing import Process


class MyProcess(Process):
    def __init__(self,name):
        super().__init__()
        self.name=name
    def run(self):
        print(os.getpid())
        print('%s 正在和女主播聊天' %self.name)

p1=MyProcess('wupeiqi')
p2=MyProcess('yuanhao')
p3=MyProcess('nezha')

p1.start() #start会自动调用run
p2.start()
# p2.run()
p3.start()


p1.join()
p2.join()
p3.join()

print('主线程')

进程之间的数据隔离问题

from multiprocessing import Process

def work():
    global n
    n=0
    print('子进程内: ',n)


if __name__ == '__main__':
    n = 100
    p=Process(target=work)
    p.start()
    print('主进程内: ',n)

守护进程

会随着主进程的结束而结束。

主进程创建守护进程

  其一:守护进程会在主进程代码执行结束后就终止

  其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children

注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止

守护进程的启动:

import os
import time
from multiprocessing import Process

class Myprocess(Process):
    def __init__(self,person):
        super().__init__()
        self.person = person
    def run(self):
        print(os.getpid(),self.name)
        print('%s正在和女主播聊天' %self.person)


p=Myprocess('哪吒')
p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行
p.start()
time.sleep(10) # 在sleep时查看进程id对应的进程ps -ef|grep id
print('主')

主进程代码执行结束守护进程立即结束

from multiprocessing import Process

def foo():
    print(123)
    time.sleep(1)
    print("end123")

def bar():
    print(456)
    time.sleep(3)
    print("end456")


p1=Process(target=foo)
p2=Process(target=bar)

p1.daemon=True
p1.start()
p2.start()
time.sleep(0.1)
print("main-------")
#打印该行则主进程代码结束,则守护进程p1应该被终止.
#可能会有p1任务执行的打印信息123,因为主进程打印main----时,p1也执行了,但是随即被终止.

socket聊天并发实例 使用多进程实现socket聊天并发-server

from socket import *
from multiprocessing import Process

server=socket(AF_INET,SOCK_STREAM)
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
server.bind(('127.0.0.1',8080))
server.listen(5)

def talk(conn,client_addr):
    while True:
        try:
            msg=conn.recv(1024)
            if not msg:break
            conn.send(msg.upper())
        except Exception:
            break

if __name__ == '__main__': #windows下start进程一定要写到这下面
    while True:
        conn,client_addr=server.accept()
        p=Process(target=talk,args=(conn,client_addr))
        p.start()

client端

from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))


while True:
    msg=input('>>: ').strip()
    if not msg:continue

    client.send(msg.encode('utf-8'))
    msg=client.recv(1024)
    print(msg.decode('utf-8'))

多进程中的其他方法 进程对象的其他方法: terminate.is_alive

from multiprocessing import Process
import time
import random

class Myprocess(Process):
    def __init__(self,person):
        self.name=person
        super().__init__()

    def run(self):
        print('%s正在和网红脸聊天' %self.name)
        time.sleep(random.randrange(1,5))
        print('%s还在和网红脸聊天' %self.name)


p1=Myprocess('哪吒')
p1.start()

p1.terminate()#关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活
print(p1.is_alive()) #结果为True

print('开始')
print(p1.is_alive()) #结果为False

进程对象的其他属性: pid和name

class Myprocess(Process):
    def __init__(self,person):
        self.name=person   # name属性是Process中的属性,标示进程的名字
        super().__init__() # 执行父类的初始化方法会覆盖name属性
        #self.name = person # 在这里设置就可以修改进程名字了
        #self.person = person #如果不想覆盖进程名,就修改属性名称就可以了
    def run(self):
        print('%s正在和网红脸聊天' %self.name)
        # print('%s正在和网红脸聊天' %self.person)
        time.sleep(random.randrange(1,5))
        print('%s正在和网红脸聊天' %self.name)
        # print('%s正在和网红脸聊天' %self.person)


p1=Myprocess('哪吒')
p1.start()
print(p1.pid)    #可以查看子进程的进程id