临界资源
一旦有对资源的共享,就必然涉及竞争限制
好比尽管有两我的去水井打水,可是水井却只有一个;合理安排的话恰好错开,可是若是安排不合理,那就会出现冲突,出现冲突怎么办?总有一个先来后到,等下就行了。
这个水井就是一个临界资源
临界资源用来表示一种公共资源或者说是共享数据,能够被多个线程使用。
可是每一次,只能有一个线程使用它,一旦临界资源被占用,其余线程要想使用这个资源,就必须等待。
当多进程访问临界资源时,好比打印机
假设A进程和B进程轮流得到CPU时间片执行,A打印数学,B打印英语,若是不进行任何的控制与限制,可能会打印出来一张纸上一道数学计算题,接下来是一段英语的状况。
因此尽管A进程和B进程轮流得到时间片运行,可是当须要访问临界资源时,一旦有一个进程已经开始使用,另外的进程就不能进行使用,只能等待。
计算机就是那个计算机,硬盘就是那个硬盘,一段代码中的某个变量(共享变量)就是那个变量.....全部的一切都是只有一份,若是对于某个点多进程同时访问,必然要作必定的限制
进程同步的主要任务是对多个相关进程在执行次序上进行协调,以使并发执行的诸进程之间能有效地共享资源和相互合做,从而使程序的执行具备可再现性
两种制约关系
既然资源访问有限制,到底有哪些场景是须要同步处理的?也就是什么时候会出现资源冲突?
看得出来,其实同步要解决的问题根本就是竞争,间接关系是赤裸裸的的竞争,共享同一个I/O就是一种竞争,尽管他们看似好像没有什么关系
直接的制约关系,源于进程间的合做,某种程度上来讲也是一种竞争,只不过是有条件的竞争,他们共享缓冲区,当缓冲区满时只能是消费者能够运行,生产者须要阻塞,这能够认为缓冲区满这种状况下,消费者独占了缓冲区,生产者不能使用了,不过这种状况下仍是说成合做比较容易理解
因此,要么是由于共享资源带来的竞争,要么就是相互合做带来的依赖。
临界区
有了临界资源的概念,就很容易理解临界区的概念,在程序中,全部的操做都是经过代码执行的,访问临界资源的那段代码就是临界区
以打水为例,因此在还没到井口,就要画一个大圈,不容许第二我的进入范围,“请站在安全黄线内”这句话熟悉么?
这就是临界区。
同步规则
如何才可以合理处理竞争或者合做依赖致使的制约?
空闲让进和忙则等待很好理解,对于临界资源,若是空闲没有被使用,谁来了以后均可以使用;若是临界资源正在被使用,那么其余后来者就须要进行等待
有限等待是指,要求访问临界资源的进程,应保证有限时间内能进入本身的临界区,本身不能傻傻的等,傻傻等受伤的是本身
让权等待是指,若是没法进入本身的临界区时,应当即释放处理机,而不能占着CPU死等,你死等就算了,别人却也不能用了
有限等待和让权等待是两个维度
你不能为了一件事情不顾一切代价等个天荒地老,太伤身了;
若是你非要花五块钱去苏宁买一台电视(等待事件发生),人家不卖给你(没法进入临界区),你就赖着不走(忙等),你就耽误别人作生意了(别的进程没法得到CPU)
有限等待和让权等待的共同特性是必须保证有条件的退出以给其余进程提供运行的机会
简单说就是有限时间内你就要走开,你得不到更要走开,你即便能获得可是时间过久也得先让一下别人
临界区的设置就是安全黄线的设置,同步规则其实就是临界区两条黄线进出规则
对于临界区,还能够进一步细分出来进入区和退出区以及剩余区
临界区算法
Peterson算法
因此临界区方式解决同步问题就是借助于算法,合理的控制对于临界区的进入与退出,而且保障可以作到:空闲让进、忙则等待、有限等待、让权等待
一种有名的算法为 Peterson
Peterson算法适用于两个进程在临界区与剩余区间交替执行。假设两个进程分别为p0 和 p1
为了表示方便,使用pi表示其中一个进程时,pj表示另一个,显然有i = 1-j(j = 1-i)
使用一个int类型变量turn 表示能够进入临界区的线程,若是turn == i,表示pi能够进入临界区
使用boolean 类型数组flag,总共两个进程,因此flag[2],用来表示哪一个进程想要进入临界区,若是flag[i] = true;表示进程pi想要进入临界区
上图红框内为进入区,蓝框内为退出区
为了进入临界区,进程pi首先设置flag[i]为true;而且设置turn为j;
显然,根据while的条件,只有flag[j] == false 或者turn == i 时,pi能够进入临界区
也就是若是我想进入的话,当对方不想进入或者当前容许我进入时,我就能够进入临界区
显然,若是只有一个进程想要进入,那么如上所述,对方不想进入时,能够进入临界区,符合空闲让进
若是两个进程都想进入,无论通过多么激烈的竞争,当执行到while时flag[0] 和 flag[1] 都是true,也就是while内部的两个条件,条件1始终是true
可是turn只会有一个值,要么0 要么1,也就是说while的第2个条件决定了谁会被while阻塞,谁可以继续执行下去
这种状况下必然可以有一个进程会进入临界区,另一个被while循环阻塞,因此符合空闲让进、忙则等待
当临界区内的进程执行结束后,会设置flag[] 标志位,若是此时另外的进程在等待,一旦设置后,其余进程就能够进入临界区(刚才已经说了,若是pi想进入,flag[j] == false 或者turn == i 时能够进入)也就是说当前进程结束后,下一个进程就可以进入了,因此知足有限等待
小结:
上面的算法,知足了经过进入区和退出区代码的设置,能够作到同步的规则
若是只有一个想要进入临界区,能够直接进入,若是两个竞争进入,只有一个可以进入,另外一个会被while循环阻塞
Peterson只是一种临界区算法,还有其余的
同步方式之信号量
1965年,荷兰学者Dijkstra 提出的信号量(Semaphores)机制是一种卓有成效的进程同步工具。
临界区算法的原理可让多进程对于临界区资源的访问串行化;
信号量机制容许多个线程在同一时刻访问同一资源,可是须要限制在同一时刻访问此资源的最大线程数目。
整型信号量
最初信号量机制被称之为整型信号量
最初由Dijkstra 把整型信号量定义为一个用于表示资源数目的整型量 S,它与通常整型量不一样,除初始化外,仅能经过两个标准的原子操做(Atomic Operation) wait(S)和 signal(S)来访问。
这两个操做一直被分别称为P、V操做(听说,听说由于Dijkstra是荷兰人。在最初论文以及大多数文献中,用P表明wait。用V表明signal。是荷兰语中高度 proberen 和增量verhogen)
Wait(S)和 signal(S)操做可描述为:
wait(S): while (S<=0);
S:=S-1;
signal(S):S:=S+1;
wait表示资源申请:若是S小于等于0(资源不足)等待,若是知足那么将会进行S-1,也就是申请资源
signal表示资源释放:每释放一次资源,资源数目 S+1
P、V操做也称之为操做原语,就是指原子操做
原子性
是指一个操做是不可中断的,要么所有执行成功要么所有执行失败(具体怎么保证的,此处能够不用关注)
多个线程并发的对资源进行访问时,借助于PV原语操做,能够有效地作到共享资源的限制访问。
可是,对于整型信号量,P操做也就是 wait(S)
wait(S): while (S<=0);
S:=S-1;
若是获取不到资源,将会持续while (S<=0);,将会永远等待,进程处于忙等状态
关于原语
wait(S)和 signal(S)这一原子操做叫作原语,原语是操做系统概念的术语,是由若干条指令组成的,用于完成必定功能的一个过程
是由若干个机器指令构成的完成某种特定功能的一段程序,具备不可分割性,即原语的执行必须是连续的,在执行过程当中不容许被中断。
原语是操做系统的核心部分组成,原语有不可中断性。它必须在管态(内核态,系统态)下执行,而且常驻内存,而个别系统有一部分不在管态下运行。
能够简单的理解是具备指定功能的操做系统提供的一个API性质的一种东西
记录型信号量
鉴于整型信号量机制中的“忙等”状况,演化出来记录型信号量
若是进程没法进入临界区,那么进入等待释放CPU资源,而且经过一个链表记录等待的进程。
记录型信号量机制在整形信号量机制的基础上增长了进程链表指针L,这也是记录型信号量名称的由来
记录型信号量 semaphore 的结构以下:
semaphore {
value:int value;
L:进程等待链表(集合);
}
value至关于整型信号量中的S,L就是一个链表(集合)
简言之,将整形信号量中的整型S,演化为一个结构,这个结构包括一个整型值,还有一个等待的进程链表
相对应整型信号量中的wait(S) 和 signal(S)能够描述为:
wait(S):
var S = semaphore;
S.value=S.value-1;
if S.value<0 then block(S.L);
signal(S):
var S = semaphore;
S.value=S.value+1;
if S.value<=0 then wakeup(S.L);
上面的操做中,均定义了一个semaphore类型的变量S
- 若是执行 wait 操做,先执行资源减一,若是此时S.value<0,说明在申请资源以前(S.value-1),原来的资源就是<=0,那么该进程阻塞,加入等待队列L中
- 若是执行 signal 操做,先执行资源加一,若是此时S.value<=0,说明在释放资源以前(),原来的资源是<0的,那么将等待链表中的进程唤醒
上面逻辑的关键之处在于:
- 当申请资源时,先进行S.value-1,一旦资源出现负数,说明须要等待,S.value的绝对值就是等待进程的个数,也就是S.L的长度
- 当资源恢复时,先进行S.value+1,已经有人释放资源了然而资源个数仍是小于等于0,说明原来就有人在等待,因此应该去唤醒
block 和 wakeup 也都是原语,也就是原子操做。
block原语,进行自我阻塞,放弃处理机,并插入到信号量链表S.L 中
wakeup原语,将S.L链表中的等待进程唤醒
若是 S.value的初值为 1,表示只容许一个进程访问临界资源,此时的信号量转化为互斥信号量,用于进程互斥。(效果就如同Peterson算法了)
AND 型信号量
针对于临界区算法或者是整型信号量或者是记录型信号量是针对各进程之间只共享一个临界资源而言的。
可是有些场景下,一个进程须要先得到两个或更多的共享资源后方能执行其任务。
假设A,B两个进程,均须要申请资源D,E
最终A,B都被阻塞,若是没有外力做用下,二者都没法从阻塞状态释放出来,这就是死锁
相关概念
锁
对于一个水井,你在打水,另外的人就要等一等,这是人类大脑意识作出来的很基本的反应(人眼识别,大脑解析而且作出反应)
可是计算机程序并无这么智能,你须要对他进行某些处理,以限制别的线程的访问,好比你能够将“安全黄线”变成一个安全门,好比厕所,进去了以后把门关上。。。
这种概念就是锁,锁就是对资源施加控制,锁指的是一种控制权
当进入临界区时,咱们称之为得到锁,得到锁以后就能够访问临界资源;
其余线程想要进入临界区,也须要先得到锁,显然,他们获取不到,由于此时,锁被当前正在执行的线程持有
当前线程结束后,将会释放锁,别得线程就能够获取这个资源的锁,而后....
死锁
锁表示一种控制权,对临界资源的访问权限,若是临界资源不止一个,就可能出现这种状况:
须要前后访问两种临界资源A和B,thread1得到了A线程的锁以后,等待得到B的锁,可是thread2得到了资源B的锁,在等待A资源的锁,这就出现了互相等待的状况
好比一条窄桥,同一时刻仅仅容许一辆车经过,若是一旦出现两辆车都走到桥的一半处,并且各执己见,怎么办?这就是死锁
解决方案
AND型信号量机制就是用于解决这种多共享资源下的同步问题的
AND 同步机制的基本思想:
将进程在整个运行过程当中须要的全部资源,一次性所有地分配给进程,待进程使用完后再一块儿释放。
只要尚有一个资源未能分配给进程,其它全部可能为之分配的资源也不分配给它。
也就是对若干个临界资源的分配,采起原子操做方式:要么把它所请求的资源所有分配到进程,要么一个也不分配。
这种思惟就是经过对“若干个临界资源“的原子分配,逻辑上就至关于一份共享资源,要么获得,也么得不到,因此就不会出现死锁
在 wait 操做中,增长了一个“AND”条件,因此被称为AND 同步,也被称为同时wait操做,即Swait(Simultaneous wait),相应的signal被称为Ssignal(Simultaneous signal)
Swait(S) 和 Ssignal(S)能够描述为:
Swait(S1,S2,…,Sn)
if(Si>=1 and … and Sn>=1){
for( i=1 to n){
Si=Si-1;
}
}else{
将进程插入到第一个资源<1 的对应的S的队列中,而且程序计数器设置到Swait的开始(好比S1 S2 都大于等于1,可是 S3<1,那么就插入到S3.L中;从头再来就是为了总体分配)
}
Ssignal(S1,S2,…,Sn)
for(i=1 to n){
Si=Si+1;
将与Si关联的全部进程从等待队列中删除,移入到就绪队列中
}
AND型信号量机制借助于对于多个资源的“批量处理”的原子操做方式,将多资源的同步问题,转换为一个“同一批资源”的同步问题
信号量集
记录型信号量机制中,只是对信号量加1(S+1 或者S.value+1) 或者 减1(S-1 或者S.value-1)操做,也就是只能得到或者释放一个单位的临界资源
若是想要一次申请N个呢?
若是进行N次的资源申请怎么办,一种方式是可使用屡次Wait(S)操做,可是显然效率较低;
另外有时候当资源数量低于某一下限值时,就不进行分配怎么办?须要在每次分配资源前检查资源的数量。
为了解决这两个问题,对AND信号量机制进一步扩展,造成了通常化的“信号量集”机制。
Swait操做可描述以下
其中S为信号量,d为需求值,而 t为下限值。
Swait(S1,t1,d1,…,Sn,tn,dn)
if( Si>=t1 and … and Sn>=tn){
for(i=1 to n){
Si=Si-di;
}
}else{
将进程插入到第一个资源Si<ti 的对应的S的队列中,而且程序计数器设置到Swait的开始(好比S1 S2 都大于等于t1,t2,可是 S3<t3,那么就插入到S3.L中;从头再来就是为了总体分配)
}
Ssignal(S1,d1,…,Sn,dn)
for(i=1 to n){
Si=Si+di;
将与Si关联的全部进程从等待队列中删除,移入到就绪队列中
}
信号量集就是AND型信号量机制将资源限制扩展到Ti
- Swait(S,d,d)。此时在信号量集中只有一个信号量 S,但容许它每次申请 d 个资源,当现有资源数少于d时,不予分配。
- Swait(S,1,1)。此时的信号量集已蜕化为通常的记录型信号量(S>1时)或互斥信号量(S=1 时)。
- Swait(S,1,0)。这是一种很特殊且颇有用的信号量操做。当 S≥1 时,容许多个进程进入某特定区;当 S 变为 0 后,将阻止任何进程进入特定区。换言之,它至关于一个可控开关。
上面的格式为(s,t,d)也就是第一个为信号量,第二个为限制,第三个为需求量
因此Swait(S,1,0)能够用作开关,只要S>=1,>=1时可分配,每次分配0个,因此只要S>=1,永远都进的来,一旦S<1,也就是0日后,那么就不知足条件,就一个都进不去
小结
临界区机制经过算法控制进程串行进入临界区,而信号量机制则是借助于原语操做(原子性)对临界资源进行访问控制
按照各类信号量机制对应的规则以及相应的原语操做,就能够作到对资源的共享同步访问,而不会出现问题
信号量机制总共有四种
整型信号量机制能够处理同一共享资源中,资源数目不止一个的状况
记录型信号量对整型信号量机制的“忙等”进行了优化,经过block以及weakup原语进行阻塞和通知
AND型信号量机制解决了对于多个共享资源的同步
信号量集是对AND的再一次优化,既可以处理多个共享资源同步的问题,还可以设置资源申请的下限,是一种更加通用的处理方式
信号量的应用
实现资源互斥访问
为使多个进程能互斥地访问某临界资源,只须为该资源设置一互斥信号量 mutex,并设其初始值为1
而后将各进程访问该资源的临界区 CS置于 wait(mutex)和 signal(mutex)操做之间便可。
这样,每一个欲访问该临界资源的进程在进入临界区以前,都要先对 mutex 执行wait操做,若该资源此刻未被访问,本次wait操做必然成功,进程即可进入本身的临界区,不然进程将会阻塞。
步骤:
.......
wait(mutex);
临界区
signal(mutex);
剩余区
.......
实现前趋关系
前驱关系就是指执行顺序,好比要求语句S1执行结束以后才能执行语句S2
在进程P1中:
S1;
signal(S);
在进程P2中
wait(S);
S2;
显然,初始时将资源S设置为0,S2须要获取到资源才会执行,而S1执行后就会释放资源
对于一个更加复杂的前驱关系图,如何实现?
从图中能够看得出来,有S2和S3依赖S1
S4 和 S5依赖S2,而S6依赖S三、S四、S5
因此,S1应该提供两个信号量,提供给S2和S3 使用
S2 应该等待S1的信号量,而且提供两个信号量给S4 和 S5
S3 应该等待S1的信号量,而且提供一个信号量给S6
S4应该等待S2的信号量,而且提供一个给S6
S5应该等待S2的信号量,而且提供一个给S6
S6应该等待S三、S四、S5
因此总共须要2+2+1+1+1 6个信号量,咱们取名为a,b,c,d,e,f,g
那么过程以下:
Var a,b,c,d,e,f,g:semaphore: =0,0,0,0,0,0,0;
P1{
S1;
signal(a);
signal(b);
}
P2{
wait(a);
S2;
signal(c);
signal(d);
}
P3{
wait(b);
S3;
signal(e);
}
P4{
wait(c);
S4;
signal(f);
}
P5{
wait(d);
S5;
signal(g);
}
P6{
wait(e);
wait(f);
wait(g);
S6;
}
有人可能会疑惑,这里的wait和signal又是什么?他就是信号量机制中的 wait 和 signal,他的内容是至关于下面的这些
同步方式之管程
虽然信号量机制是一种既方便、又有效的进程同步机制,
但每一个要访问临界资源的进程都必须自备同步操做 wait(S)和 signal(S),这就使大量的同步操做分散在各个进程中。
这不只
给系统的管理带来了麻烦,并且还会因同步操做的使用不当而致使系统死锁。
在解决上述问题的过程当中,便产生了一种新的进程同步工具——管程(Monitors)。
因此管程也能够这么理解:它可以确保临界资源的同步访问,而且还不用将大量的同步操做分散到各个进程中。
管程的定义
系统中的各类硬件资源和软件资源,都可用数据结构抽象地描述其资源特性
好比一个IO设备,有状态(空闲仍是忙时?),以及能够对他采起的操做(读取仍是写入?)以及等待该资源的进程队列来描述,因此就能够从这三个维度抽象描述一个IO设备,而不关注他们的内部细节
又好比,一个集合,可使用集合大小,类型,以及一组可执行操做来描述,好比Java中的ArrayList,有属性,还有方法
能够把对该共享数据结构实施的操做定义为一组过程
好比资源的请求和释放过程定义为request 和 release
进程对共享资源的申请、释放和其它操做,都是经过这组过程对共享数据结构的操做来实现的
类比到JavaBean的话,这些操做就如同setter和getter方法,全部对于指定对象的操做访问都须要经过getter和setter方法 ,相似,全部对共享数据结构实施的操做,都须要借助于这一组过程。
这组过程还能够根据资源的状况,或接受或阻塞进程的访问,确保每次仅有一个进程使用共享资源,这样就能够统一管理对共享资源的全部访问,实现进程互斥
仍是类比到JavaBean,就是至关于又增长了几个方法,这些方法提供了更多的逻辑判断控制
表明共享资源的数据结构,以及由对该共享数据结构实施操做的一组过程所组成的资源管理程序,共同构成了一个操做系统的资源管理模块,咱们称之为管程。
Dijkstra于1971年提出:
把全部进程对某一种临界资源的同步操做都集中起来,构成一个所谓的秘书进程。
凡要访问该临界资源的进程,都需先报告秘书,由秘书来实现诸进程对同一临界资源的互斥使用。
管程的概念经由Dijkstra提出的概念演化而来,由Hoare和Hanson于1973年提出。
定义以下:
一组相关的数据结构和过程一并称为管程。
Hansan的定义:一个管程定义了一个数据结构和能为并发进程在该数据结构上所执行的一组操做,这组操做能同步进程和改变管程中的数据。
因此管程的核心部分是对共享数据抽象描述的
数据结构
以及能够对该数据结构实施操做的一组
过程。
使用数据结构对共享资源进行抽象描述,那么必然要初始化数据,好比一个队列Queue,有属性size,这是一个抽象的数据结构,那么一个具体的队列到底有多大?你须要设置size 的值,因此对于管程还包括
初始化
一个基本的管程定义以下:
Minitor{
管程内部的变量结构以及说明;
函数1(){
}
......
函数N(){
}
init(){
对管程中的局部变量进行初始化;
}
}
管程特色
管程就是管理进程,管程的概念就是设计模式中“依赖倒置原则”,依赖倒置原则是软件设计的一个理念,IOC的概念就是依赖倒置原则的一个具体的设计
管程将对共享资源的同步处理封闭在管程内,须要申请和释放资源的进程调用管程,这些进程再也不须要自主维护同步。有了管程这个大管家(门面模式?)进程的同步任务将会变得更加简单。
管程是墙,过程是门,想要访问共享资源,必须经过管程的控制(经过城墙上的门,也就是通过管程的过程)
而管程每次只准许一个进程进入管程,从而实现了进程互斥
管程的核心理念就是至关于构造了一个管理进程同步的“IOC”容器。
管程是一个语言的组成成分(非操做系统支持部分),管程的互斥访问彻底由编译程序在编译时自动添加上,无需程序员关心,并且保证正确
通常的 monitor 实现模式是编程语言在语法上提供语法糖,而如何实现 monitor 机制,则属于编译器的工做
好比,Java中使用synchronized时,这是否是一种管程理念?你只是写了一个synchronized关键字(语法糖),多线程的共享同步彻底不用你操心了
(注意:
并非全部的语言都支持管程的概念)
条件变量
管程能够保证互斥,同一时刻仅有一个进程进入管程,因此他必然须要同步工具,如两个同步操做原语 wait和 signal,他还须要互斥量用以控制管程进入的同步
当某进程经过管程请求得到临界资源而未能知足时,管程便调用 wait 原语使该进程等待,并将其排在等待队列上
仅当另外一进程访问完成并释放该资源以后,管程调用signal原语,唤醒等待队列中的队首进程
可是,仅仅这个互斥量是不够的
好比,若是须要处理以前提到过的“执行顺序控制”,如何控制前驱关系?
当一个进程调用了管程,在管程中时被阻塞或挂起,直到阻塞或挂起的缘由解除,而在此期间,若是该进程不释放管程,则其它进程没法进入管程,被迫长时间地等待。
因此还须要其余的信号量用于针对其余条件进行同步,这些就是条件变量,因此一个完整的管程定义为:
Minitor{
管程内部的变量结构以及说明;
condition 条件变量列表;
函数1(){
}
......
函数N(){
}
init(){
对管程中的局部变量进行初始化;
}
}
条件变量就是当调用管程过程的进程没法运行时,用于阻塞进程的一种信号量
管程中对每一个条件变量都须予以说明,其形式为:Var x,y:condition。
对条件变量的操做仅仅是wait和signal,条件变量也是一种抽象数据类型,每一个条件变量保存了一个链表,用于记录因该条件变量而阻塞的全部进程,同时提供的两个操做便可表示为 x.wait和x.signal,其含义为:
① x.wait:正在调用管程的进程因 x 条件须要被阻塞或挂起,则调用 x.wait 将本身插入到x条件的等待队列上,并释放管程,直到x条件变化。此时其它进程可使用该管程。
② x.signal:正在调用管程的进程发现 x 条件发生了变化,则调用 x.signal,从新启动一个因 x 条件而阻塞或挂起的进程。若是存在多个这样的进程,则选择其中的一个,若是没有,则继续执行原进程,而不产生任何结果。这与信号量机制中的 signal操做不一样,由于后者老是要执行s:=s+1操做,于是总会改变信号量的状态。
若是有进程Q因x条件处于阻塞状态, 当正在调用管程的进程P执行了x.signal操做后,进程Q 被从新启动,此时两个进程 P和Q,如何肯定哪一个执行,哪一个等待,可采用下述两种方式之一进行处理:
(1) P等待,直至Q 离开管程或等待另外一条件。
(2) Q等待,直至P离开管程或等待另外一条件。
采用哪一种处理方式,固然是各执一词。
Hoare 采用了第一种处理方式
而 Hansan 选择了二者的折衷,他规定管程中的过程所执行的signal 操做是过程体的最后一个操做,因而,进程P执行signal操做后当即退出管程,于是进程Q立刻被恢复执行。
总结
进程控制是操做系统的一种硬性管理,是必需要有的,若是没有进程控制,就没办法合理安排运行进程, 根本没法完成状态的切换维护等。
进程的同步是一种软逻辑上的,若是不作,并不会致使系统出问题或者进程没法运行,可是若是不进行同步,结果却极可能是错误的,因此也是必须作的
类比装修的话,进程控制就是硬装,不装无法住,总归要水电搞搞好,进程同步就是家具家电和软装,硬装后住进去不会出现“生存问题”(至少有水喝有电用),可是你要是连个热水壶都没有是打算要喝凉水么
进程同步的概念多很复杂抽象,由于毕竟是概念表述,没有涉及到具体的实现细节。
进程同步的核心是对于临界资源的访问控制,也就是对于临界区的访问。
无论是临界区算法仍是信号量机制仍是管程机制,终归也都是控制进入临界区的不一样的实现思路。
每种不一样的算法、机制都各自有各自的特点,场景。
信号量机制将临界资源的访问的互斥,演化为能够多个进程访问资源(整型信号量),记录型信号量对整型信号量机制进行优化,处理了忙等的问题
而后继续演化出AND型,能够对不一样的资源进行同步,而不只仅是同一种资源
最后发展为信号量集的形式,能够对不一样的资源、不一样的个数、不一样的限制进行处理,变得更为通用。
管程更多的是一种设计思惟,管程就是管理进程的程序,进程对于资源的同步操做全都依赖管程这一“大管家”,管程是编程语言级别的,不须要程序员进行过多的处理,通常会提供语法糖
须要注意并非全部的语言都有管程的概念(Java是有的),管程让你从同步的细节中解放出来,能够在不少场景下简化同步的实现。
管程的概念是“线程同步”的“IOC”,大大简化了同步的代价。
无论临界区算法仍是信号量机制仍是借助于管程,他们都是一种同步工具,能够认为他们就是一组“方法”,“方法”的逻辑就是本文前面介绍的原理
在须要进程同步问题的解决思路中,能够直接使用“封装好的方法”
以上,尽管都是在说操做系统关于进程的同步的处理,其实,在同步问题上进程和线程的设计理念是相通的
由于这本质上都是在说并发----多道程序运行的操做系统,一般使用轮转时间片的方式并发的运行多个进程