你好!这里是风筝的博客,
欢迎和我一起交流。
说到同步,不得不说著名的生产者-消费者问题(producer-consumer problem),解决好生产者-消费者问题,就解决了并发进程的同步问题。
“生产者-消费者”问题描述如下:
有一个有限缓冲区和多个消费者和生产者,它们分别不停地把产品放入缓冲区中拿走产品。一个生产者在缓冲区满的时候必须等待,一个消费者在缓冲区空的时候也必须待。
int k; /*缓存区大小*/
item buffer[k]; /*缓存区*/
int counter=0,in=0,out=0;
//生产者进程
process producer(void)
{
while (true)
{
produce an item in nextp;/*生产一个产品*/
if (counter==k) /*缓冲满时*/
sleep(producer); /*生产者睡眠*/
buffer[in]=nextp; /*将一个产品放入缓冲区*/
in=(in+1) mod k; /*指针推进*/
counter++; /*缓冲内产品数加1*/
if (counter==1) /*缓冲之前为空*/
wakeup(consumer); /*加进一件产品并唤醒消费者*/
}
}
//消费者进程
process consumer(void)
{
while (true)
{
if (counter==0) /*缓冲为空*/
sleep (consumer); /*消费者睡眠*/
nextc=buffer[out]; /*取一个产品到nextc*/
out=(out+1) mod k; /*指针推进*/
counter--; /*缓冲内产品数减1*/
if (counter==k-1) /*缓冲之前为满*/
wakeup(producer); /*取走一件产品并唤醒生产者*/
consume the item in nextc;/*消费一个产品*/
}
}
上面的程序,两组进程顺序执行是没问题的,但是若并发执行,就会出现错误结果,出错的根子在于进程之间共享了变量counter,对counter 的访问未加限制。生产者和消费者进程对counter 的交替执行会使其结果不唯一。例如生产者执行counter++后进程切换到消费者,消费者里又对counter–操作,显然是不对的。
更为严重的是生产者和消费者进程的交替执行会造成系统死锁。
假定消费者读取counter 发现它为0。此时调度程序切换到生产者,生产者加入一个产品,将counter 加1,现在counter 等于1 了。它想当然地推想由于counter刚刚为0,所以,此时消费者一定在睡眠,于是生产者调用wakeup 来唤醒消费者。不幸的是,消费者还未去睡觉,唤醒信号被丢失掉。当消费者再次运行时,因已测到counter为0,于是去睡眠。这样生产者迟早会填满缓冲区,然后,去睡觉,形成了进程都永远处于睡眠状态。
这次是因为它们访问缓冲区的速率不匹配造成的,需要调整并发进程的进行速度。并发进程间的这种制约关系称进程同步,交互的并发进程之间通过交换信号或消息来达到调整相互速率,保证进程协调运行的目的。
1965年,一位荷兰的大佬就提出了一个同步工具:信号量和PV操作。
P(semaphore):将信号量的值减一,若结果小于零,则执行P操作的进程被阻塞,进入等待队列中;若结果大于等于零,则执行P操作的进程继续执行。
V(semaphore):将信号量的值加一,若结果小于等于零,则执行V操作的进程唤醒队列中的一个等待进程,自己则继续执行;若结果大于零,则执行V操作的进程继续执行。
利用信号量即可解决并发进程的竞争问题,又可解决并发进程的协作问题。
semaphore empty =1;
semaphore full=0;
item buffer[1]; /*缓存区*/
int in=0,out=0;
process producer(void)
{
while (true)
{
produce an item in nextp;/*生产一个产品*/
P(empty);
buffer[0]=nextp; /*将一个产品放入缓冲区*/
V(full);
}
}
process consumer(void)
{
while (true)
{
P(full);
nextc=buffer[0]; /*取一个产品到nextc*/
V(empty);
consume the item in nextc;/*消费一个产品*/
}
}
这样就很好的解决了消费者-生产者问题,但是,这个程序的模型是一个生产者(semaphore empty =1),如果有k个生产者呢?
int k; /*缓存区大小*/
semaphore empty =k;
semaphore full=0;
semaphore mutex=1;
item buffer[k]; /*缓存区*/
int in=0,out=0;
process producer(void)
{
while (true)
{
produce an item in nextp;/*生产一个产品*/
P(empty);
P(mutex);
buffer[in]=nextp; /*将一个产品放入缓冲区*/
in=(in+1) mod k; /*指针推进*/
V(mutex);
V(full);
}
}
process consumer(void)
{
while (true)
{
P(full);
P(mutex);
nextc=buffer[out]; /*取一个产品到nextc*/
out=(out+1) mod k; /*指针推进*/
V(mutex);
V(empty);
consume the item in nextc;/*消费一个产品*/
}
}
这样,只需要把资源(产品)再用一组PV操作保护起来就好了。