进程和线程
对于无线程系统:
进程是一个具有独立功能的程序关于某个数据集合的一次运行活动,它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。进程实体由三部分构成:程序段
、相关的数据段
、PCB(进程控制块)
。所谓创建/撤销进程,实际上就是创建/撤销进程实体中的PCB。
对于有线程系统:
在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。
1)线程是进程的实体,一个进程可以有多个线程,多个线程可以并发执行。
2)进程作为系统中拥有资源的基本单位,但线程并不拥有系统资源,它仅有一点必不可少的、能保证独立运行的资源。进程中的多个线程可以共享该进程所拥有的资源。
3)不同线程之间的独立性要比不同进程之间的独立性低得多。因为进程不允许别的进程来访问它的资源(除了共享全局变量),但同一个进程下的所有线程可以共享该进程的内存地址空间和资源。
4)线程的创建和撤销的系统开销明显小于进程的创建和撤销。
Ⅰ 拥有资源
进程是资源分配的基本单位,但是线程几乎不拥有资源(只拥有线程栈、寄存器和程序计数器等少量资源),线程可以访问所属进程的资源。
Ⅱ 调度
线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
Ⅲ 系统开销
由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。
Ⅳ 通信方面
线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助 IPC。
对比维度 | 多进程 | 多线程 | 总结 |
---|---|---|---|
数据共享、同步 | 数据共享复杂,需要用 IPC;数据是分开的,同步简单 | 因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂 | 各有优势 |
内存、CPU | 占用内存多,切换复杂,CPU 利用率低 | 占用内存少,切换简单,CPU 利用率高 | 线程占优 |
创建销毁、切换 | 创建销毁、切换复杂,速度慢 | 创建销毁、切换简单,速度很快 | 线程占优 |
编程、调试 | 编程简单,调试简单 | 编程复杂,调试复杂 | 进程占优 |
可靠性(鲁棒性?) | 进程间不会互相影响 | 一个线程挂掉将导致整个进程挂掉 | 进程占优 |
分布式 | 适应于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单 | 适应于多核分布式 | 进程占优 |
优劣 | 多进程 | 多线程 |
---|---|---|
优点 | 编程、调试简单,可靠性较高 | 创建、销毁、切换速度快,内存、资源占用小 |
缺点 | 创建、销毁、切换速度慢,内存、资源占用大 | 编程、调试复杂,可靠性较差 |
几种通讯方式总结:
1.管道:速度慢,容量有限,只有父子进程能通讯
2.FIFO:任何进程间都能通讯,但速度慢
3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
4.信号量:不能传递复杂消息,只能用来同步
5.共享内存:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
同步和通信
同步与通信很容易混淆,它们的区别在于:
通信是一种手段,而同步是一种目的。也可以说,为了能够达到同步的目的,需要进行通信,传输一些同步所需要的信息。通信目的主要是用于同步。
通信与同步的概念不必区分过于清晰,很多时候通信和同步的意思是想通的,一般同步是针对线程,通信是针对进程。进程间也有同步问题,而IPC是同步的实现手段。
同步与互斥
互斥:某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的,线程间不需要知道彼此的存在。
同步:在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问,线程间知道彼此的存在。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
线程的同步:
为什么需要线程同步:线程有时候会和其他线程共享一些资源,比如内存、数据库等。当多个线程同时读写同一份共享资源的时候,可能会发生冲突。因此需要线程的同步,多个线程按顺序访问资源。
《UNIX环境高级编程第三版》线程同步机制:
ReleaseSemaphore
函数将当前可用资源数加1。如果信号量的取值只能为0或1,那么信号量就成为了互斥量;临界区的概念:各个进程中对临界资源(互斥资源/共享变量,一次只能给一个进程使用)进行操作的程序片段
1)生产者消费者问题
使用一个缓冲区来存放数据,只有缓冲区没有满,生产者才可以写入数据;只有缓冲区不为空,消费者才可以读出数据。
2)哲学家进餐问题
五个哲学家围着一张圆桌,每个哲学家面前放着食物。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,需要先拿起自己左右两边的两根筷子,并且一次只能拿起一根筷子。筷子的数量和哲学家相等,所以每只筷子必须由两位哲学家共享。
为了防止死锁的发生,可以设置两个条件(临界资源):
3)读者-写者问题
允许多个进程同时对数据进行读操作,但是不允许读和写以及写和写操作同时发生。
管程
管程 (英语:Monitors,也称为监视器,c 语言不支持管程) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。
管程是为了解决信号量在临界区的 PV 操作上的配对的麻烦,把配对的 PV 操作集中在一起,生成的一种并发编程方法。其中使用了条件变量这种同步机制。
管程将共享变量以及对这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,这样只能通过管程提供的某个过程才能访问管程中的资源。进程只能互斥地使用管程,使用完之后必须释放管程并唤醒入口等待队列中的进程。
管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,否则其它进程永远不能使用管程。
信号量机制功能强大,但使用时对信号量的操作分散,而且难以控制,读写和维护都很困难。因此后来又提出了一种集中式同步进程——管程。
其基本思想是将共享变量和对它们的操作集中在一个模块中,操作系统或并发程序就由这样的模块构成。这样模块之间联系清晰,便于维护和修改,易于保证正确性。
从语言的角度看,管程主要有以下特性:
(1)模块化。管程是一个基本程序单位,可以单独编译;
(2)抽象数据类型。管程中不仅有数据,而且有对数据的操作;
(3)信息掩蔽。管程外可以调用管程内部定义的一些函数,但函数的具体实现外部不可见;
对于管程中定义的共享变量的所有操作都局限在管程中,外部只能通过调用管程的某些函数来间接访问这些变量。因此管程有很好的封装性。
创建-终止
应该注意以下内容:
线程的状态与转换
与进程相同,创建、就绪、运行、阻塞、终止。
一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。
孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。
由于孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害。
一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过 wait() 或 waitpid() 获取了子进程信息后才会释放。
如果子进程退出,而父进程并没有调用 wait() 或 waitpid(),那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。
僵尸进程通过 ps 命令显示出来的状态为 Z(zombie)。
系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。
要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时所有的僵尸进程就会变成孤儿进程,从而被 init 所收养,这样 init 就会释放所有的僵死进程所占有的资源,从而结束僵尸进程。
进程调度算法(进程的优先级)
(1)先来先服务(FCFS,First-Come-First-Served): 此算法的原则是按照作业到达后备作业队列(或进程进入就绪队列)的先后次序来选择作业(或进程)。按照请求的顺序进行调度。
(2)短作业优先(SJF,Shortest Process Next)/ 短进程优先(SPF
):按估计运行时间最短的顺序进行调度。长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。
(3)最短剩余时间优先算法:按估计剩余时间最短的顺序进行调度。
(4)高响应比优先(HRRN,Highest Response Ratio Next): 按照高响应比((已等待时间+要求运行时间)/ 要求运行时间)优先的原则,在每次选择作业投入运行时,先计算此时后备作业队列中每个作业的响应比RP然后选择其值最大的作业投入运行。
(5)多级队列调度算法:(1)多级队列是为需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,..。进程在第一个队列没执行完,就会被移到下一个队列。(2)每个队列优先权也不同,最上面的优先权最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。(3)如果一个进程需要执行 100 个时间片,采用时间片轮转调度算法需要交换 100 次,但在多级队列方式下只需要交换 7 次就可以执行完。
(6)时间片轮转调度算法(RR,Round-Robin):将所有就绪进程按 FCFS (先来先服务) 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。时间片轮转算法的效率和时间片的大小有很大关系。因为进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,会导致进程切换得太频繁,在进程切换上就会花过多时间。
(7)实时调度算法:实时系统要求一个请求在一个确定时间内得到响应。分为硬实时和软实时,前者必须满足绝对的截止时间,后者可以容忍一定的超时。
多线程会出现什么问题? 1、数据安全问题,非线程安全;2、一个线程的崩溃会导致整个进程崩溃;3、死锁。
线程安全
保证线程安全的方法
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
非线程安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
C++创建线程的方式,怎么实现多线程?
锁和死锁
所谓的锁,说白了就是内存中的一个整型数,拥有两种状态:空闲状态和上锁状态。加锁时,判断锁是否空闲,如果空闲,修改为上锁状态,返回成功;如果已经上锁,则返回失败。解锁时,则把锁状态修改为空闲状态。为了保证数据操作的一致性,操作系统引入了锁机制,用于保证临界区代码的安全。
死锁是指两个或两个以上的进程(线程)在运行过程中因争夺资源而造成的一种僵局(Deadly-Embrace) ) ,若无外力作用,这些进程(线程)都将无法向前推进。
死锁的原因是在多线程(或多进程)环境中,资源具有竞争性,而程序运行推进顺序不当,导致出现一种状态:多个线程(或进程)互相依赖其它线程(或进程)释放它们持有的资源,在未改变这种状态之前都不能向前推进。
产生条件:
处理死锁的基本方法
预防死锁:设置某些限制条件,破坏四个必要条件中的一个或几个。
避免死锁:进行有序资源分配,在资源的动态分配过程用某种方法防止系统进入不安全状态。
优点:较弱限制条件可获得较高系统资源利用率和吞吐量。
缺点:有一定实现难度。
检测死锁:允许系统在运行过程中发生死锁,但可设置检测机制及时检测死锁的发生,再采取适当措施加以清除。
解除死锁:检测出死锁后,采取适当的措施来解除死锁。死锁解除的方法:
鸵鸟策略
银行家算法
银行家算法思想——死锁避免策略:
1)当前状态下,某进程申请资源;
2)系统假设将资源分给该进程,满足它的需求;
3)检查分配后的系统状态是否是安全的,如果是安全,就确认本次分配;如果系统是不安全的,就取消本次分配并阻塞该进程。(第三步又称安全算法)
避免死锁实质:在进程资源分配时,如何使系统不进入不安全状态。
虚拟内存
为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序都拥有自己的地址空间,这个地址空间被分成大小相等的页,这些页被映射到物理内存;但不需要所有的页都在物理内存中,当程序引用到不在物理内存中的页时,由操作系统将缺失的部分装入物理内存。
这样,对于程序来说,逻辑上似乎有很大的内存空间,只是实际上有一部分是存储在磁盘上,因此叫做虚拟内存。
虚拟内存的优点是让程序在逻辑上可以获得更多的可用内存。
内存管理单元(MMU)管理着逻辑地址和物理地址的转换,其中的页表(Page table)存储着页(逻辑地址)和页框(物理内存空间)的映射表,页表中还包含有效位(是在内存还是磁盘)、访问位(是否被访问过)、修改位(内存中是否被修改过)、保护位(只读还是可读写)。逻辑地址:页号+页内地址(偏移);每个进程一个页表,放在内存,页表起始地址在PCB/寄存器中。
页面置换算法有哪些?
(1)最佳置换算法(Optimal):即选择那些永不使用的,或者是在最长时间内不再被访问的页面置换出去。(它是一种理想化的算法,性能最好,但在实际上难于实现)。
(2)先进先出置换算法FIFO:该算法总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面予以淘汰。
(3)最近最久未使用置换算法LRU(Least Recently Used):该算法是选择最近最久未使用的页面予以淘汰,系统在每个页面设置一个访问字段,用以记录这个页面自上次被访问以来所经历的时间T,当要淘汰一个页面时,选择T最大的页面。
(4)Clock置换算法:也叫最近未用算法NRU(Not RecentlyUsed)。该算法为每个页面设置一位访问位,将内存中的所有页面都通过链接指针链成一个循环队列。当某页被访问时,其访问位置“1”。在选择一页淘汰时,就检查其访问位,如果是“0”,就选择该页换出;若为“1”,则重新置为“0”,暂不换出该页,在循环队列中检查下一个页面,直到访问位为“0”的页面为止。由于该算法只有一位访问位,只能用它表示该页是否已经使用过,而置换时是将未使用过的页面换出去,所以把该算法称为最近未用算法。
(5)最少使用置换算法LFU:该算法选择最近时期使用最少的页面作为淘汰页。
LRU算法-最近最久未使用
一种页面置换算法。原理是:
虽然无法知道将来要使用的页面情况,但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。
为了实现 LRU,需要在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面是最近最久未访问的。因为每次访问都需要更新链表,因此这种方式实现的 LRU 代价很高。
局部性原理
内存抖动(颠簸)
颠簸本质上是指频繁的页调度行为。进程发生缺页中断时必须置换某一页。然而,其他所有的页都在使用,它置换一个页,但又立刻再次需要这个页。因此会不断产生缺页中断,导致整个系统的效率急剧下降,这种现象称为颠簸。内存颠簸的解决策略包括:
原文:https://www.cnblogs.com/zicmic/p/13110316.html