一、信号量
- 整形信号量:表示系统中某种资源的数量(不满足"让权等待")。
int S=1;//初始化整型信号量S,表示当前系统中可用的资源数
void wait(int S){ //wait原语,相当于"进入区"
while(S<=0); //如果资源数不够,就一直循环等待
S=S-1; //如果资源数够,占用一个资源
}
void signal(int S){ //signal原语,相当于"退出区"
S=S+1; //使用完资源后,退出区释放资源
}
- 记录型信号量:s.value<0时,|s.value|代表链表中已被阻塞的该信号进程的数目(即等待进入临界区的)遵循了"让权等待"原则。
//记录型信号量的定义
typedef struct{
int value; //剩余资源数
struct process *L; //等待队列
}semaphore;
//某进程需要使用资源时,通过wait原语申请。
void wait(semaphore S){
S.value--;
if(S.value<0){
block(S.L);
}
}
//进程使用完资源后,通过signal原语释放
void wait(semaphore S){
S.value++;
if(S.value<=0){
wakeup(S.L);
}
}
二、信号量机制实现同步与互斥
- 互斥:设置互斥信号量mutex,初值为1。
semaphore mutex=1; //初始化信号量
P1(){
...
P(mutex); //使用临界资源前需要加锁
临界区代码段...
V(mutex); //使用临界资源后需要解锁
...
}
P2(){
...
P(mutex);
临界区代码段...
V(mutex);
...
}
- 同步:必须保证"一前一后"(前V后P)执行的操作。设置同步信号量S,初值为0。注:同步:要为每一对前驱关系各设置一个信号量。P、V操作必须成对出现。
二、信号量机制实现同步与互斥
- 互斥:设置互斥信号量mutex,初值为1。
semaphore mutex=0; //初始化信号量,初值为0
P1(){
代码1;
V(S);
代码2;
}
P2(){
P(S);
代码3;
代码4;
}
三、生产者消费者问题
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,常用的方法有信号灯法等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。
如何实现:
- 生产者、消费者共享一个初始为空、大小为n的缓冲区
- 缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。
- 缓冲区不空时,消费者才能从中取出产品,否则必须等待。
- 缓冲区时临界资源,各进程必须互斥访问。
semaphore mutex=1; //互斥信号量,实现对缓冲区的互斥访问
semaphore empty=n; //同步信号量,表示空闲缓冲区的数量
semaphore full=0; //同步信号量,表示产品数量,即非空缓冲区的数量
producer(){
while(1){
生产一个产品;
P(empty);
P(mutex);
讲产品放入缓冲区;
V(mutex);
V(full);
}
}
consumer(){
while(1){
生产一个产品;
P(full);
P(mutex);
从缓冲区取出一个产品;
V(mutex);
V(empty);
使用产品;
}
}
四、读者与写者问题
读者—写者问题(Readers-Writers problem)也是一个经典的并发程序设计问题,是经常出现的一种同步问题。计算机系统中的数据(文件、记录)常被多个进程共享,但其中某些进程可能只要求读数据(称为读者Reader);另一些进程则要求修改数据(称为写者Writer)。就共享数据而言,Reader和Writer是两组并发进程共享一组数据区,要求:
(1)允许多个读者同时执行读操作;
(2)不允许读者、写者同时操作;
(3)不允许多个写者同时操作。
写者优先
semaphore rw=1; //用于实现对文件的互斥访问
int count=0; //记录当前有几个读进程在访问文件
semaphore mutex=1; //用于保证对count变量的互斥访问
semaphore w=1; //用于实现"写优先"
writer(){
while(1){
P(w);
p(rw);
写文件...
V(rw);
V(w);
}
}
reader(){
while(1){
P(w);
p(mutex);
if(count==0)p(rw);
count++;
V(mutex);
V(w);
读文件...
P(mutex);
count--;
if(count==0)V(rw);
V(mutex);
}
}
五、哲学家进餐问题
有五个哲学家,他们的生活方式是交替地进行思考和进餐,哲学家们共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支筷子,平时哲学家进行思考,饥饿时便试图取其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐,该哲学家进餐完毕后,放下左右两只筷子又继续思考。
约束条件
(1)只有拿到两只筷子时,哲学家才能吃饭。
(2)如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子。
(3)任一哲学家在自己未拿到两只筷子吃完饭前,不会放下手中已经拿到的筷子。
如何实现
- 可以对哲学家施加一些限制条件,比如允许四个哲学家同时进餐,这样可以保证至少有一个科学家是可以拿到左右两只筷子的。
semaphore chopstick[5]={1,1,1,1,1}; //用一个信号量表示一只筷子
semaphore num=4; //限制为4个哲学家
void philosophyer(int i){
while(1){
P(num);
P(chopstick[i]);
P(chopstick[(i+1)%5]);
...//eat
V(chopstick[i]);
V(chopstick[(i+1)%5]);
V(num);
...//think
}
}
- 要求奇数号哲学家先拿左边筷子,然后再拿右边的筷子,而偶数号哲学家相反。用这种方法保证如果相邻的两个奇偶号哲学家都想吃饭,那么只会有其中一个拿起第一只筷子,另一个会直接阻塞,这就避免了占有一支后再等待另一只的情况。
semaphore chopstick[5]={1,1,1,1,1}; //用一个信号量表示一只筷子
void philosophyer(int i){
if(i%2==0)
while(1){
P(chopstick[(i+1)%5]); //偶数哲学家先拿右边筷子
P(chopstick[i]);
...//eat
V(chopstick[(i+1)%5]);
V(chopstick[i]);
...//think
}
else
while(1){
P(chopstick[i]); //奇数哲学家先拿左边筷子
P(chopstick[(i+1)%5]);
...//eat
V(chopstick[i]);
V(chopstick[(i+1)%5]);
...//think
}
}
六、顾客与理发师问题
一个理发师,一把理发椅,n把等候理发的顾客椅子,如果没有顾客则理发师便在理发椅上睡觉 ,当有一个顾客到达时,首先看理发师在干什么,如果理发师在睡觉,则唤醒理发师理发,如果理发师正在理发,则查看是否有空的顾客椅子可坐, 如果有,坐下等待,如果没有,则离开。
int chairs=5,waiting=0; //定义椅子数和正在等待顾客数量
semaphore baber=0; //理发师信号量
semaphore customer=0; //顾客信号量
semaphore mutex=1; //互斥信号量
void barber(){
while(1){
P(customer);
P(mutex);
waiting--;
V(mutex);
...//cut
V(barber);
}
}
void comstomer(){
while(1){
P(mutex);
if(waiting<chairs){ //店里还有座
waiting++;
V(customer);
V(mutex);
P(barber);
...//get
}else
V(mutex);
}
}