同步机制
硬件同步机制
- 关中断
在进入锁测试之前关闭中断,直到完成锁测试并上锁才能打开中断。 - 利用Text-and-Set指令实现进程互斥
lock=FALSE时候资源空闲,lock=TRUE的时候资源正在被使用。
/* TS指令的一般性描述 */
boolean TS(boolean *lock) {
boolean old;
old = *lock;
*lock = TRUE;
return old;
}
/* 利用TS指令实现互斥 */
do{
…
while TS(&lock); // 使用的时候就一直在这个true的循环里走
critical section; //
lock = FALSE; // 当操作打开锁的时候,
remainder section;
} while(TRUE)
- 使用Swap指令实现进程互斥
/* swap指令的一般性描述 */
void swap(boolean *a, boolean *b) // 交换
{
boolean temp;
temp = *a;
*a = *b;
*b = temp;
}
/* 利用swap指令实现互斥 */
Do {
key = TRUE // 让key=True
do {
swap(&lock, &key); // 交换lock和key 也就是让lock=true
} while (key != FALSE); // 如果key不是false,证明交换前lock不是false,也证明当初是锁着的,那就一直循环锁死。什么时候解锁了再说。
critical section;
lock = FALSE;
remainder section;
} while(TRUE)
软件方式
- 信号量机制
- 整型信号量
- 记录型信号量
- AND型信号量
- 信号量集
整型信号量
- wait(s)和signal(s)两个操作
// S表示资源数目的整型量S
/*wait(s)操作*/
wait(S){
while (S≤0){ /*do no-op*/ };
S = S – 1; // 表明有一个需要资源
}
/* signal(s)操作*/
signal(S){
S++; //让信号量 +1 表明资源数增加了一个
}
// 这种方式无法做到让权等待
记录型信号量
// 引入进程阻塞机制,解决忙等问题
// 增加了对阻塞进程的记录
/*记录型的数据结构*/
typedef struct{
int value; // 资源数目
struct process_control_block *list; // 等待进程链表
}
/*wait(s) 请求资源*/
wait(semaphore *S){
S->value--; // 资源数量 -1 表明当前需求了一个信号量
if(S->value < 0){
block(S->list); // 如果资源数量少于0,那就把请求加入阻塞队列
}
}
/*signal操作 释放资源*/
signal(semaphore *S){
S->value++; // 释放一个资源
if(S->value <= 0){
wakeup(S->list); // 如果是负数证明还有阻塞队列,打开一下
}
}
记录型信号量特点
-
S.value的含义
S.value = 1,这时候wait(S)一下就进入了阻塞状态,保证了只允许一个资源访问,为互斥状态。
且当S大于0,代表资源充足,S为负数,代表资源需求量。S为0,代表阻塞。 -
遵循让权等待
-
资源访问过程是原语,不存在执行一半这种情况。
AND型信号量
- 基本思想:将多次对多个信号量申请修改为一次,用一个语句完成,要么一次获得所有,要么一个也得不到
- 目的:防止资源不同导致的死锁。
信号量集
- 一次需要N个某类资源,需要进行N次wait(S)操作,这样效果低下,且容易出现数量问题死锁,
- Swait(S,d,d)对信号量S,允许申请d个,当自远少于d个不予分配
- Swait(S,1,1),等价于一般的记录型信号量或互斥信号量
- Swait(S,1,0)。低于0个不予分配,只能申请1个,相当于可控开关,可以做if语句用
信号量的应用
利用信号量实现进程的互斥
// 设有程序A和B,都需要同一个临界区
semaphore mutex = 1; // 互斥信号量,这里表示的是mutex->value = 1;
Pa(){
while(1){
wait(mutex){};
// Pa in 临界区
signal(mutex);
// Pa 其他操作
}
}
Pb(){
while(1){
wait(mutex){};
// Pb in 临界区
signal(mutex);
// Pb 其他操作
}
}
利用信号量实现前驱关系
p1(){
S1;
signal(s); // 释放信号量
}
p2(){
wait(s); // 必须等到p1释放信号量才能执行p2d的剩余操作
S2;
}
void main(){
semaphore s;
s.value = 0;
cobegin() // 并发执行开始标记
p1();
p2();
coend
}
// 这里可以根据前趋图实现更加复杂的前驱关系,
p1(){S1;signal(a);signal(b);}
p2(){wait(a);S2;signal(c);signal(d);}
p3(){wait(c);S3;signal(e);}
p4(){wait(d);S4;signal(f);}
p5(){wait(b);S5;signal(g);}
p6(){wait(e);wait(f);wait(g);S6;}
main() {
semaphore a,b,c,d,e,f,g;
a.value=b.value=c.value=d.value=e.value=0;
f.value=g.value=0;
cobegin
p1();p2();p3();p4();p5();p6();
coend
}
经典的进程同步机制
生产者-消费者问题
- 描述:这是一个生产者生产消息后,消费者消费的合作关系
- 消费者消费的空白缓冲块由生产者生产
- 进程在队列操作上是互斥的。
- 缓冲池是有大小限制的,也就是最多就生产X个
- 确定信号量
- full:表示缓冲池中满缓冲块数量
- empty:表示缓冲池中空缓冲块数量
- mutex,表示对缓冲池的互斥访问
- 因此可以确定,缓冲区数量必然等于full+empty,mutex=1来执行互斥访问。
int in=0,out=0;
item buffer[n];
semaphore mutex=1,empty=n,full=0;
// 生产者
void producer(){
do{
produce an item nextp;
.....
wait(empty); // 空白块加工
wait(mutex); // 访问临界区
buffer[in] = nextp; // 处理临界区
in = (in + 1)%n;
signal(mutex); // 结束访问
signal(full); // 满区块 增加
}while(True);
}
void consumer(){
do {
wait(full); // 消费满内容块
wait(mutex); // 互斥信号量
nextc=buffer[out]; // 缓冲块处理 临界区
out= (out+1) % n;
signal(mutex); //
signal(empty); // 增加空白块
consume the item in nextc;
…
}while(TRUE)
}
void main(){
cobegin
producer();
consumer();
coend;
}
- P操作的顺序很重要,不然会产生死锁
利用AND信号量解决生产者-消费者问题
int in=0,out=0;
item buffer[n];
semaphore mutex=,empty=n,full=0;
void producer(){
do{
producer an item nextp;
Swait(empty, mutex);
buttfer[in]=nextp;
in = (in+1)%n;
Ssignal(mutex,full);
}while(1); // 这里其实就是把双重申请换成了单独S申请,其他的是一样的
}
// 下略
哲学家进餐问题
-
五个哲学家,五根儿筷子
-
解决冲突的第一种方案,现制定顺序,比如先拿左边的筷子,
-
如果出现都拿了自己左手边的,等待右手边的筷子空闲下来,那就死锁了,所以要想办法改变一下,变成四个哲学家拿左边的,这样就能保证至少一个人能吃上饭,然后释放。
-
但是这玩意代码咋写…
-
代码略
reader & writer 问题
- 允许同时读
- 不允许同时写
- 不允许有读有写
- 设计:读rmutex,写wmutex
- 读的时候加上写锁,写的时候加上写锁和读锁
// reader开始读
void reader(){
do{
wait(rmutex); // 读的时候,上读锁
if(readcount==0)
wait(wmutex); // 第一个读者开始的时候不允许其他人写
readcount++;
signal(remutex); // 释放临界区(readcount)
.....
// 写完了,需要destroy了
wait(rmutex); // 访问读者的临界区
readcount --;
if(readcount==0)
signal(wmutex); // 最后一个读者都没有了这个时候允许写
signal(rmutex);
}while(1);
}
void writer(){
do{
wait(wmutex); // 写临界区锁开始打开
........
write...
.......
signal(wmutex); // 临界区锁关闭
}while(1);
}
- 感悟
- 基本上mutex是用来表示临界区的打开关闭的,wait打开临界区,上临界区锁,signal关闭临界区,上临界区锁…
- 记录数据的,基本上可以算作是临界区里的,也就是这里的readcount ++ - - 对它的操作必须是互斥的。