操作系统期末复习——第六章 进程同步
作者:互联网
第六章 线程同步
1.背景
防止竞争条件问题——多个进程共享数据,需要保持数据的一致性
防止竞争条件的关键:确保操作共享数据的代码段执行同步(互斥运行)
2.临界区问题
多个进程同时操作共享数据时,每个进程操作共享数据的代码段,这个代码段成为临界区
解决竞争条件的关键:
- 确保单个进程在临界区内执行
- 确保其他进程可以进入临界区问题
解决临界区问题需要满足如下条件:
- 互斥
- 前进:没有进程在临界区,确保其他进程能进入临界区
- 有限等待:一个进程请求进入临界区,等待时间必须有限
3.Peterson's算法
前提条件:加载和存储指令是原子指令,不可被中断
设置两个变量:
int turn//表示哪个进程可以进入临界区
bool flag[n]//表示哪个进程想要进入临界区,n为进程数量
do{
flag[i] = true;
turn = i;
while(flag[j] && yurn == j);
//临界区代码
flag[i] = false;
//退出区代码
}while(true);
- 没有turn的话,两个程序并行执行可能导致一直卡在while循环
4.硬件同步
单、多处理器系统都可以通过禁用中断的方式解决临界区问题,单是代价高!
硬件同步主要采用原子指令(不可中断)——TestAndSet() / swap()
TestAndSet()指令:
bool TestAndSet(bool* lock){
//lock代表临界区是否上锁,true则上锁、false则不上锁,默认false
bool old = *lock;
lock = ture;
return old;
}
//进程判断代码
do{
while(TestAndSet(&lock));
//临界区代码
lock = false;
//结束区代码
}
swap()指令
//lock同上
void swap(bool* a,bool* b){
bool tmp = *a;
*a = *b;
*b = tmp;
}
//进程判断代码
while(true){
old = true;
while(old == true)swap(&old,&lock);
//临界区代码
lock = false;
//结束区代码
}
Peterson's,TestAndSet,swap都会出现忙等待问题!
5.信号量
无忙等待的同步工具
用S来表示信号量,S为整数变量(一般表示临界资源的数量)
只能通过标准原子操作来访问信号量
-
wait(S): 也称P(S)
wait(S){ while(S<=0); S-- }
-
signal(S): 也称V(S)
signal(S){ S++; }
封锁代码的实现
do{
wait(S);
//临界区代码
signal(S);
//结束区代码
}while(true);
信号量的实现关键是保证wait()和signal()操作的原子执行,没有两个进程能同时对一个信号量执行以上操作
无忙等待的信号量实现
为了让忙等待的进程挂起,在可以进入临界区时重新启动。
将信号量定义如下:
typedef struct{
int value;//整数值,资源数量
struct process *list;
}semaphore
wait和signal定义如下:
wait(semaphore *S){
S->value--;
if(S->value < 0){
//将该进程加入等待队列S->list中
block();//挂起该进程
}
}
signal(semaphore *S){
S->value++;
if(S->value <= 0){
//从S->list中取出一个进程P
wake(P);//唤醒取出的进程P
}
}
死锁与饥饿
6.经典同步问题
6.1有限缓冲问题
假定缓冲池有n个缓冲项,每个缓冲项能存一个数据项
- 缓冲池满不能写
- 缓冲池空不能读
- 读和写互斥
解决方法:
- 定义信号量empty——有几个空缓冲项,初始化为n
- 定义信号量full——有几个满缓冲项,初始化为0
- 定义信号量mutex——保证读写互斥,初始化为1
生产者(写)代码实现:
while(true){
//生产一个缓冲项
wait(empty);
wait(mutex);
//写入缓冲池
signal(mutex);
signal(full);
}
消费者(读)代码实现:
while(true){
wait(full);//full>0,只要缓冲项有数据就读
wait(mutex);
//读出一个缓冲项
signal(mutex);
signal(empty);
//使用读出的数据
}
6.2读者-写者问题
问题规定:
- 读写互斥,写写互斥
- 多人读可以同时访问数据,但是要知道读的人数
解决方法:
- 定义信号量wrt——读写互斥
- 定义countread——记录读的人数
- 定义信号量mutex——写countread互斥
写者进程代码实现:
while(true){
wait(wrt);
//写
signal(wrt);
}
读者进程代码实现:
while(true){
wait(mutex);
countread++;
if(countread == 1){//第一位读者加wrt锁
wait(wrt);
}
signal(mutex);
//读
wait(mutex);
countread--;
if(countread == 0){//最后一位读者释放wrt锁
signal(wrt);
}
signal(mutex);
}
6.3哲学家用餐问题
- 哲学家只会思考,吃饭
- 1碗米饭,5根筷子,每个哲学家之间各有一根筷子
- 同时又两根筷子才能吃饭
问题:可能产生死锁(每个哲学家同时请求他们左手的筷子)
几个解决方案:
- 规定至多有4个哲学家开始吃饭
- 单数位上的哲学家必须先申请左边的筷子,双数位上的哲学家必须先申请右边的筷子
- 每个哲学家申请筷子时互斥,哲学家必须一次申请完两根筷子
7.管程
为了解决不当使用信号量而产生的死锁问题
管程是一种程序结构(很像java中的类)
- 采用面向对象的方法,简化进程间同步控制
- 任何时刻最多只有一个线程执行管程代码
- 可以临时放弃管程的互斥访问,等待事件出现时恢复
为什么引入管程:
- 把各进程临界区集中管理
- 防止违法同步操作
- 用高级语言程序书写便于程序的正确性识别
管程的组成:
- 一个锁,控制管程访问的互斥
- 0或多个条件变量,管理共享数据的互斥访问
- 一个条件变量对应一个等待队列,每个条件变量有以一个wait(),signal()操作
标签:复习,进程同步,signal,信号量,互斥,临界,第六章,进程,wait 来源: https://www.cnblogs.com/zhaoqinmumiand/p/16367422.html