进程的同步与通信

来源:互联网 发布:字符串的全排列 java 编辑:程序博客网 时间:2024/06/08 15:34
学完了进程同步与通信,总结一下。

对于线程的执行,我们既需要互斥,又需要并行。
互斥可以使得线程的执行具有可再现性,并有一个确定的执行结果。
而并行可以让我们多线程存在时执行效率提高,并且共享了资源。

那么要使得互斥和并行同时存在,就引入了线程间的同步。
同步使得协调多线程对共享数据的互斥访问。
(p.s.同步也是互斥的别名。那为什么叫同步呢?因为同步使得多线程协调访问共享资源。打个比方,线程1和2同时来了,都需要资源A和B。假如线程1必须在2获取资源B之后才能去获取资源A,B,那自然操作系统就让线程2先按2的需要去执行。然后线程2获取B后执行了一个结果,使得线程1可以获取资源A,那这时1获取A,2等待A,然后2获取A,1获取B,这样子线程1,2既互斥又同步。
互斥是从微观上说的,如果不互斥的话,共享资源在多个线程同时享用的时候是有很多不确定性的 ; 而同步是从宏观上讲的,总的来看,经过协调,多线程是近似同步完成的。 )

那实现同步就需要实现两个事情:1.共享资源时的互斥 2.共享资源时的通信交流


这里引入几个概念:
临界资源:一次只允许一个线程(进程)访问的资源。
临界区:正在访问临界资源的一段代码。一个线程在临界区执行时其他线程不能进入临界区。
进入区:进入临界区前,检查临界资源闲忙的一段代码。
退出区:退出临界区前,释放临界资源的一段代码。
(这三个区都是代码段的一段代码)

另外,线程同步有四个原则:
空闲让进,忙则等待,有限等待,让权等待

实现:
1.共享资源时的互斥
方法有三个:
(1)禁用中断
(2)软件方法
(3)原子操作指令锁
分析比较三者的实现原理和优劣之处。
(1)禁用中断:在访问临界资源时关中断,访问完了再开中断,这样访问的这个过程就不会被其他线程打断,也就实现了互斥访问。
那关中断期间来的所有中断请求都会等到开中断以后再执行。
优点:简单易行
缺点:1.只适用于单处理机。如果是多处理机?
            2.线程无法停止:如果系统执行过程中出现问题了,没有中断,它就一错到底了,系统会崩溃的 ;如果执行中没有问题,但是只要不开中断其他线程可能会饥饿。
            3.临界区可能很长,这样关中断时间会很久。


(2)软件方法
:主要就是Dekker算法和Peterson算法来实现。
Dekker算法:(Dekker算法的详解在下一篇文章)
flag[i] = true;
while (flag[j]) {
if (turn != i) {
flag[i] = false;
while (turn != i);
flag[i] = true;
}
}
/* critical section code */
...
turn = j;
flag[i] = false;
/* remainder section */

Peterson算法:

flag用于标记我想不想进(或者我现在有没有准备好进)。
turn用于标记该谁进了。
do {
flag[i] = true;
turn = j;
while ( flag[j] && turn == j);
CRITICAL SECTION
flag[i] = false;
REMAINDER SECTION
} while (true);
Peterson算法就是每次在我想进的时候(flag[i]=true)都谦让一下别人(turn=j),如果你也想进那你就先进吧,我等等你。如果你不想进入那我就进入了。
这样就不会出现我们俩同时进去的情况了。
缺点:很复杂。且需要忙等待,占用CPU(一致判断临界资源的使用情况)。

(3)原子操作指令锁:主要就是TS指令和Swap指令,自旋锁和互斥锁
TS指令:设置Lock为全局bool变量,利用Test&Set指令实现对临界区的加锁和解锁。
while(true)
{
while(TS(lock)); //lock 为true时,有其他进程在占用临界
//区,当前进程等待 执行CSi
lock=false; //释放临界区
……}

Swap指令:设lock为全局布尔变量(初值为假),每个进程设一个局部布尔变量key。利用Swap指令,可实现对临界区的加锁与解锁。

void Pi( ){

boolean key = true;
do{
Swap (lock, key);
}while(key) //循环等待lock值为false,这时交换了lock和key,循环结束且lock=ture
critical section
lock = false;

}

这时不同的等待方式可以实现两种锁:
自旋锁:忙等待。
互斥锁:无忙等待。设置了一个阻塞队列。

优点:1.简单易行
2.适用于多临界区(每个临界区设置一个锁)
3.适用于单处理机,共享主存的多处理机中任意多个线程(进程)的访问。
缺点:可能会死锁,饥饿。忙等也消耗CPU时间。

2.共享资源时的通信交流
两种办法:借助信号量或管程。

信号量
分两种:
1.互斥信号量 mutex,初值设为1
2.资源信号量 可以有多个,根据资源的数量。用于协作。
一般来说先判断资源信号量再判断互斥信号量。
注意,信号量归操作系统管理,设置的不要太多,尽可能少。

总结一点:
1.由P(),V()操作,判断是否可以进入临界区。能进就进,不能进就等。可以忙等也可以阻塞。
2.信号量和锁是一样的,都是控制线程是否进入临界区,实现方法都是三种:禁用中断,软件方法和原子操作指令。不同的地方就是,如果只有一个互斥信号量,也就是mutex时,就相当于锁了。
所以和锁相比,信号量一个特点就是可以用于多线程间协作(利用资源信号量)。

值得注意的是:信号量同步应用问题分析步骤
*根据题目描述抽象出问题涉及的几类进程(有几种活动对象就需要几类进程),给出进程执行的伪码;
*分析并标注每个进程与其他进程之间的制约关系;
*定义资源信号量:资源信号量的不同状态数一般就是需要的信号量数;
*定义互斥信号量:有共享的临界资源存在时需要该信号量;
*删去多余的信号量(保证并发进程执行结果可再现性的前提下,信号量越少越好);
*对信号量值进行初始化;
*将信号量的P、V操作添加到进程的伪代码中。

信号量集:将线程在运行中所需要的临界资源一次性全部分配给该线程,如果有一个资源不能到位,所有其它资源也都将不再分配给该线程。用完后所有的临界资源一次性释放。
酱紫就避免了死锁,但是资源利用率降低。

管程:
 管程作为一个模块,它的类型定义如下:
    monitor_name = MoNITOR; 
       共享变量说明; 
       define 本管程内部定义、外部可调用的函数名表; 
       use 本管程外部定义、内部可调用的函数名表; 
       内部定义的函数说明和函数体 
       { 
         共享变量初始化语句; 
       }
 
从语言的角度看,管程主要有以下特性:
  (1)模块化。管程是一个基本程序单位,可以单独编译; 
  (2)抽象数据类型。管程是中不仅有数据,而且有对数据的操作; 
  (3)信息掩蔽。管程外可以调用管程内部定义的一些函数,但函数的具体实现外部不可见; 
对于管程中定义的共享变量的所有操作都局限在管程中,外部只能通过调用管程的某些函数来间接访问这些变量。因此管程有很好的封装性。(类)
为了保证共享变量的数据一致性,管程应互斥使用。 管程通常是用于管理资源的,因此管程中有进程等待队列和相应的等待和唤醒操作。在管程入口有一个等待队列,称为入口等待队列。当一个已进入管程的进程等待时,就释放管程的互斥使用权;当已进入管程的一个进程唤醒另一个进程时,两者必须有一个退出或停止使用管程。在管程内部,由于执行唤醒操作,可能存在多个等待进程(等待使用管程),称为紧急等待队列,它的优先级高于入口等待队列。 
因此,一个进程进入管程之前要先申请,一般由管程提供一个enter过程;离开时释放使用权,如果紧急等待队列不空,则唤醒第一个等待者,一般也由管程提供外部过程leave。
管程内部有自己的等待机制。管程可以说明一种特殊的条件型变量:var c:condition;实际上是一个指针,指向一个等待该条件的PCB队列。对条件型变量可执行wait和signal操作:
    wait(c):若紧急等待队列不空,唤醒第一个等待者,否则释放管程使用权。执行本操作的进程进入C队列尾部;
    signal(c):若C队列为空,继续原进程,否则唤醒队列第一个等待者,自己进入紧急等待队列尾部。

总结一点:
1.管程一个特点就是在管程内部执行的线程可以临时放弃互斥访问,让给其他线程。
2.管程和信号量相比:信号量机制功能强大,但使用时对信号量的操作分散,而且难以控制,读写和维护都很困难。因此后来又提出了一种集中式同步进程——管程。其基本思想是将共享变量和对它们的操作集中在一个模块中,操作系统或并发程序就由这样的模块构成。这样模块之间联系清晰,便于维护和修改,易于保证正确性. 


0 0
原创粉丝点击