Linux 并发控制的几个例子
(生产者与消费者,哲学家进餐,读者写者)
今天重新开始项目,查阅资料的时候发现了几个很典型有意思的并发控制的问题,自己学习了一下,也整理了一下。
1、生产者和消费者问题:
a、单缓冲区问题描述:生产者向消费者提供产品,它们共享一个有界缓冲区,生产者向其中投放产品,消费者从中取得产品。同时,每个进程都互斥的占用CPU。假定生产者和消费者是互相等效的,只要缓冲区未满,生产者就可以把产品送入缓冲区,类似的,只要缓冲区未空,消费者便可以从缓冲区中取走产品并消费它。生产者—消费者的同步关系将禁止生产者向已满的缓冲区中放入产品,也禁止消费者从空的缓冲区中获取产品
问题分析:需要定义两个信号量,一个用于互斥访问缓冲区,另一个用于生产者与消费者之间的同步。s1=1; s2=0;
伪代码:
生产者进程
消费者进程
while(1)
{
printf(“I'm producing!\n”);
down_interruptible(&s1);
printf(“I'm putting a product into buffer\n”);
up(&s1);
up(&s2);
}
while(1)
{
down_interruptible(&s2);
down_interruptible(&s1);
printf(“I'm getting a product\n”);
up(&s1);
printf(“I'm consuming a product\n”);
}
理解:
s1用来控制互斥访问,即在同一个时刻只能有读或者写对缓冲区进行操作。
s2用来控制缓冲区的商品量,当s2的量不为0时,即生产者进行生产之后,才能进行消费,与s1不同的是,s1只能控制对产品的访问,即不能同时访问,但是对产品的量没有规定,而s2规定其必须有生产后才能进行消费。
问题:
s2只规定了在产品为0时不能进行消费,即对消费的最低值进行规定,但是没有规定生产的最高值,即生产者—消费者的同步关系将禁止生产者向已满的缓冲区中放入产品这个问题没有解决。
也可以直接将s2用变量buffer代替,设置上下限,即可弥补以上功能
生产者进程
消费者进程
while(1)
{
If(buffer
printf(“I'm producing!\n”);
down_interruptible(&s1);
printf(“I'm putting a product into buffer\n”);
up(&s1);
buffer++;
}
while(1)
{
if(buffer>0)
down_interruptible(&s1);
printf(“I'm getting a product\n”);
up(&s1);
printf(“I'm consuming a product\n”);
buffer--;
}
与上一个的区别在于当flag满足不了条件时,进程不会睡眠(也可在else中设置睡眠功能)。
b. 多生产者、多消费者、n个缓冲区问题描述:有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者进程并发执行,在两者之间设置了n个缓冲区,生产者将产品放入一个缓冲区中,消费者可以从一个缓冲区中取走产品去消费。要求生产者进程与消费者进程必须保持同步,即不允许生产者进程向一个满的缓冲区放产品,也不允许消费者从一个空的缓冲区取产品。
问题分析:该问题貌似比a问题复杂的多,首先我们定义一个数组buffer[n],来表示n个缓冲区,还需要定义两个变量:in 表示要存入的缓冲区的下标,out表示要取产品的缓冲区的下标。定义三个信号量:s1用于实现对缓冲池的互斥操作,empty表示空缓冲区的个数,full表示满缓冲区的个数。初值:in=out=0; s1=1; full=0; empty=n;
生产者进程
消费者进程
while(1)
{
printf(“I'm producing!\n”); // 生成数据
down(&empty); // 将空槽数目减 1
down(&s1); // 进入临界区
printf(“I'm putting a product into buffer[in]\n”); // 将新数据放入缓冲区
in = (in+1)%n; //主要目的是没有n,只有0
up(&mutex); // 离开临界区
up(&full); // 将满槽的数目加 1
}
while(1)
{
down_interruptible(&full);
down_interruptible(&s1);
printf(“I'm getting a product from buffer[out]\n”);
out = (out+1)%n;
up(&s1);
up(&empty);
printf(“I'm consuming a product\n”);
}
理解:这个问题和第一个实质是一样的,唯一的区别在于empty的使用上,加入了empty的信号量,实质上就是增加了缓冲区上限,实现了上限的功能。
即当缓冲区满了之后,empty此时为0,此时生产者进程进入休眠,等待消费者进程消耗产品。
full则是实现了下限的功能,当full为0时,即没有产品,消费者必须进入休眠,等待生产。
2.哲学家进餐问题
问题描述:五个哲学家共用一个圆桌,分别坐在周围的五张椅子上,在圆桌上有五只碗和五只筷子,他们交替进行思考和进餐。哲学家饥饿时便试图取最靠近他的两只筷子,当同时获得两只筷子时便可用餐,用餐完毕后放下筷子。
问题分析:五只筷子为临界资源,定义包含五个元素的信号量数组来实现对筷子的互斥使用。chopstick[5],五个信号量的初值都为1。
伪代码:
第i(i=0,1,2,3,4)个哲学家进程:
while(1)
{
down_interruptible(&chopstick[i]);
down_interruptible(&chopstick[(i+1)%5]);
printf(“I'm eating!\n”);
up(&chopstick[i]);
up(&chopstick[(i+1)%5);
}
理解:
其本质是资源的互斥占用。这个资源有两个。
3.读者写者问题:
问题描述:一个文件可被多个进程共享,reader进程读取该文件,而writer进程负责写文件,允许多个reader进程同时读取文件,但不允许一个writer进程和其他reader进程或writer进程同时访问文件。
问题分析:进程对文件互斥访问的实现可借助一个信号量就可以搞定,但是我们需要引入一个count变量来记录reader进程的个数,对这个变量的访问也是互斥的,所以也需要引入一个信号量。定义信号量rs实现对count的互斥访问,定义ws实现对文件的互斥访问。两信号量初值都为1
伪代码:
读者进程
写者进程
while(1)
{
down(&rs);
down(&ws);
count++;
up(&rs);
printf(“I’m reading”);
read();
down(&rs);
count--;
if(count==0)
{up(ws);}
up(rs);
}
while(1)
{ down(&ws);
if (count == 0)
{
printf(“I’m writing”);
write();
up(&ws;)
}
}
rs用来保护count在reader之间是独立访问的,count的作用是来保证读者没有了之后才能进行写。
ws用来保证文件在reader与writer之间是独立访问的。
参考http://blog.sina.com.cn/s/blog_4770ef020101gjyx.html