1. 生产者-消费者问题
典型的生产者-消费者问题如下图所示。生产者和消费者线程共享一个由n个槽的有限缓冲区,生产者线程反复生成新的item并将其插入缓冲区尾部,消费者线程不断从缓冲区头部取出这些item并消费他们。
实现代码如下所示:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
#include <stdio.h>
#include<unistd.h> #include <stdlib.h> #include <time.h> #include<semaphore.h> #include "pthread.h" #define BUFFER_SIZE 2 /* Circular buffer of integers. */ struct prodcons { int buffer[BUFFER_SIZE]; /* the actual data */ sem_t lock; /* mutex ensuring exclusive access to buffer */ int readpos, writepos; /* positions for reading and writing */ sem_t empty; /* signaled when buffer is not empty */ sem_t full; /* signaled when buffer is not full */ }; /* Initialize a buffer */ void init( struct prodcons *prod) { sem_init(&prod->lock, 0, 1); sem_init(&prod->empty, 0, BUFFER_SIZE); sem_init(&prod->full, 0, 0); prod->readpos = 0; prod->writepos = 0; } /* Store an integer in the buffer */ void put( struct prodcons *prod, int data) { sem_wait(&prod->empty); sem_wait(&prod->lock); /* Write the data and advance write pointer */ prod->buffer[(++prod->writepos) % (BUFFER_SIZE)] = data; /*Signal that the buffer is now not empty */ sem_post(&prod->lock); sem_post(&prod->full); } /* Read and remove an integer from the buffer */ int get( struct prodcons *prod) { int data; sem_wait(&prod->full); sem_wait(&prod->lock); /* Read the data and advance read pointer */ data = prod->buffer[(++prod->readpos) % (BUFFER_SIZE)]; /* Signal that the buffer is now not full */ sem_post(&prod->lock); sem_post(&prod->empty); return data; } #define OVER (- 1) struct prodcons buffer; /*--------------------------------------------------------*/ void *producer( void *data) { int n; for (n = 0; n < 5; n++) { printf( "producer sleep 1 second......\n"); sleep( 1); printf( "put the %d product\n", n); put(&buffer, n); } for(n = 5; n < 10; n++) { printf( "producer sleep 3 second......\n"); sleep( 3); printf( "put the %d product\n", n); put(&buffer, n); } put(&buffer, OVER); printf( "producer stopped!\n"); return NULL; } /*--------------------------------------------------------*/ void *consumer( void *data) { int d = 0; while ( 1) { printf( "consumer sleep 2 second......\n"); sleep( 2); d = get(&buffer); printf( "get the %d product\n", d); // d = get(&buffer); if (d == OVER ) break; } printf( "consumer stopped!\n"); return NULL; } /*--------------------------------------------------------*/ int main( int argc, char *argv[]) { pthread_t th_a, th_b; void *retval; init(&buffer); pthread_create(&th_a, NULL, producer, 0); pthread_create(&th_b, NULL, consumer, 0); /* Wait until producer and consumer finish. */ pthread_join(th_a, &retval); pthread_join(th_b, &retval); return 0; } |
2、理发师问题
理发师问题:
一个理发店由一个有几张椅子的等待室和一个放有一张理发椅的理发室组成。
1. 若没有要理发的顾客,则理发师去睡觉;
2. 若一顾客进入理发店,理发师正在为别人理发,且等待室有空椅子,则该顾客就找张椅子按顺序坐下;
3. 若一顾客进入理发店,理发师在睡觉,则叫醒理发师为该顾客理发;
4. 若一顾客进入理发店且所有椅子都被占用了,则该顾客就离开。
二、伪码实现:
互斥信号量:mutex 用来互斥对临界变量count的访问
计数信号量 customers用来记录等候的顾客数据,barbers用来记录等待的理发师数,这里barbers只有两种取值,要不是0要不是1
临界变量:count由理发师进程和顾客进程共同访问,用来记录在椅子上等着的顾客数
N 椅子数,为最多等候的顾客数
理发师进程
![](http://www.cnblogs.com/Images/OutliningIndicators/None.gif)
1
2 3 4 5 6 7 8 9 10 11 12 13 |
Barbers()
{ While( true) { P(customers) P(mutex) Count--; V(mutex) V(barbers) 理发师理发; } } |
顾客进程
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Customer()
{ while( true) { P(mutex); if(count < N) { Count++ V(mutex); V(customer) P(barbers); 顾客被理发 } else V(mutex); } } |
三 、Linux实现
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
//2007-12-23 10:46:11 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <errno.h> #include <sys/ipc.h> #include <semaphore.h> #include <fcntl.h> #define n 5 time_t end_time; /*end time*/ sem_t mutex, customers, barbers; int count = 0; //等待理发的顾客数 void *barber( void *arg); void *customer( void *arg); int main( int argc, char *argv[]) { pthread_t id1, id2; int ret = 0; end_time = time( NULL) + 20; //初使化信号量的个数 sem_init(&mutex, 0, 1); sem_init(&customers, 0, 0); ret = sem_init(&barbers, 0, 1); if( 0 != ret) perror( "sem init."); //初使化两个线程 ret = pthread_create(&id1, NULL, barber, NULL); if( 0 != ret) perror( "create barbers."); ret = pthread_create(&id2, NULL, customer, NULL); if( 0 != ret) perror( "create customers."); //让顾客进程先阻塞 pthread_join(id2, NULL); pthread_join(id1, NULL); exit( 0); } /*理发师进程*/ void *barber( void *arg) { while(time( NULL) < end_time || count > 0) { sem_wait(&customers); //p(customers) sem_wait(&mutex); //p(mutex) count--; printf( "Barber:cut hair,count is:%d.\n", count); sem_post(&mutex); //v(mutex); sem_post(&barbers); //v(barbers) 通知已进来的,在等理发师的顾客进程 sleep( 3); } } /*customer*/ /*顾客进程*/ void *customer( void *arg) { while(time( NULL) < end_time) { sem_wait(&mutex); //p(mutex) if(count < n) { count++; printf( "Customer:add count,count is:%d.\n", count); sem_post(&mutex); //v(mutex) sem_post(&customers); //v(customers) sem_wait(&barbers); //p(barbers) } else { //v(mutex) 注意 如果个客户数已满了,则把互斥锁放掉, sem_post(&mutex); } sleep( 1); } /*end of while(time()<end_time)*/ } /*customer*/ |
问题描述
哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家就餐问题有时也用米饭和筷子而不是意大利面和餐叉来描述,因为很明显,吃米饭必须用两根筷子。
哲学家从来不交谈,这就很危险,可能产生死锁,每个哲学家都拿着左手的餐叉,永远都在等右边的餐叉(或者相反)。
即使没有死锁,也有可能发生资源耗尽。例如,假设规定当哲学家等待另一只餐叉超过五分钟后就放下自己手里的那一只餐叉,并且再等五分钟后进行下一次尝试。这个策略消除了死锁(系统总会进入到下一个状态),但仍然有可能发生“活锁”。如果五位哲学家在完全相同的时刻进入餐厅,并同时拿起左边的餐叉,那么这些哲学家就会等待五分钟,同时放下手中的餐叉,再等五分钟,又同时拿起这些餐叉。
在实际的计算机问题中,缺乏餐叉可以类比为缺乏共享资源。一种常用的计算机技术是资源加锁,用来保证在某个时刻,资源只能被一个程序或一段代码访问。当一个程序想要使用的资源已经被另一个程序锁定,它就等待资源解锁。当多个程序涉及到加锁的资源时,在某些情况下就有可能发生死锁。例如,某个程序需要访问两个文件,当两个这样的程序各锁了一个文件,那它们都在等待对方解锁另一个文件,而这永远不会发生。
解法
服务生解法
一个简单的解法是引入一个餐厅服务生,哲学家必须经过他的允许才能拿起餐叉。因为服务生知道哪只餐叉正在使用,所以他能够作出判断避免死锁。
为了演示这种解法,假设哲学家依次标号为A至E。如果A和C在吃东西,则有四只餐叉在使用中。B坐在A和C之间,所以两只餐叉都无法使用,而D和E之间有一只空余的餐叉。假设这时D想要吃东西。如果他拿起了第五只餐叉,就有可能发生死锁。相反,如果他征求服务生同意,服务生会让他等待。这样,我们就能保证下次当两把餐叉空余出来时,一定有一位哲学家可以成功的得到一对餐叉,从而避免了死锁。
资源分级解法
另一个简单的解法是为资源(这里是餐叉)分配一个偏序或者分级的关系,并约定所有资源都按照这种顺序获取,按相反顺序释放,而且保证不会有两个无关资源同时被同一项工作所需要。在哲学家就餐问题中,资源(餐叉)按照某种规则编号为1至5,每一个工作单元(哲学家)总是先拿起左右两边编号较低的餐叉,再拿编号较高的。用完餐叉后,他总是先放下编号较高的餐叉,再放下编号较低的。在这种情况下,当四位哲学家同时拿起他们手边编号较低的餐叉时,只有编号最高的餐叉留在桌上,从而第五位哲学家就不能使用任何一只餐叉了。而且,只有一位哲学家能使用最高编号的餐叉,所以他能使用两只餐叉用餐。当他吃完后,他会先放下编号最高的餐叉,再放下编号较低的餐叉,从而让另一位哲学家拿起后边的这只开始吃东西。
尽管资源分级能避免死锁,但这种策略并不总是实用的,特别是当所需资源的列表并不是事先知道的时候。例如,假设一个工作单元拿着资源3和5,并决定需要资源2,则必须先要释放5,之后释放3,才能得到2,之后必须重新按顺序获取3和5。对需要访问大量数据库记录的计算机程序来说,如果需要先释放高编号的记录才能访问新的记录,那么运行效率就不会高,因此这种方法在这里并不实用。
这种方法经常是实际计算机科学问题中最实用的解法,通过为分级锁指定常量,强制获得锁的顺序,就可以解决这个问题。
Chandy/Misra解法
1984年,K. Mani Chandy和J. Misra提出了哲学家就餐问题的另一个解法,允许任意的用户(编号P1, ..., Pn)争用任意数量的资源。与资源分级解法不同的是,这里编号可以是任意的。
- 对每一对竞争一个资源的哲学家,新拿一个餐叉,给编号较低的哲学家。每只餐叉都是“干净的”或者“脏的”。最初,所有的餐叉都是脏的。
- 当一位哲学家要使用资源(也就是要吃东西)时,他必须从与他竞争的邻居那里得到。对每只他当前没有的餐叉,他都发送一个请求。
- 当拥有餐叉的哲学家收到请求时,如果餐叉是干净的,那么他继续留着,否则就擦干净并交出餐叉。
- 当某个哲学家吃东西后,他的餐叉就变脏了。如果另一个哲学家之前请求过其中的餐叉,那他就擦干净并交出餐叉。
这个解法允许很大的并行性,适用于任意大的问题。
///信号量的解决方法//
规定在拿到左侧的筷子后,先检查右面的筷子是否可用。如果不可用,则先放下左侧筷子,
等一段时间再重复整个过程。
分析:当出现以下情形,在某一个瞬间,所有的哲学家都同时启动这个算法,拿起左侧的筷
子,而看到右侧筷子不可用,又都放下左侧筷子,等一会儿,又同时拿起左侧筷子……如此
这样永远重复下去。对于这种情况,所有的程序都在运行,但却无法取得进展,即出现饥饿,
所有的哲学家都吃不上饭。
(2) 描述一种没有人饿死(永远拿不到筷子)算法。
考虑了四种实现的方式(A、B、C、D):
A.原理:至多只允许四个哲学家同时进餐,以保证至少有一个哲学家能够进餐,最终总会释
放出他所使用过的两支筷子,从而可使更多的哲学家进餐。以下将room 作为信号量,只允
许4 个哲学家同时进入餐厅就餐,这样就能保证至少有一个哲学家可以就餐,而申请进入
餐厅的哲学家进入room 的等待队列,根据FIFO 的原则,总会进入到餐厅就餐,因此不会
出现饿死和死锁的现象。
伪码:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
semaphore chopstick[
5] = {1,1,1,1,1};
semaphore room = 4; void philosopher( int i) { while( true) { think(); wait(room); //请求进入房间进餐 wait(chopstick[i]); //请求左手边的筷子 wait(chopstick[(i + 1) % 5]); //请求右手边的筷子 eat(); signal(chopstick[(i + 1) % 5]); //释放右手边的筷子 signal(chopstick[i]); //释放左手边的筷子 signal(room); //退出房间释放信号量room } } |
B.原理:仅当哲学家的左右两支筷子都可用时,才允许他拿起筷子进餐。
方法1:利用AND 型信号量机制实现:根据课程讲述,在一个原语中,将一段代码同时需
要的多个临界资源,要么全部分配给它,要么一个都不分配,因此不会出现死锁的情形。当
某些资源不够时阻塞调用进程;由于等待队列的存在,使得对资源的请求满足FIFO 的要求,
因此不会出现饥饿的情形。
伪码:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
semaphore chopstick[
5] = {1,1,1,1,1};
void philosopher( int I) { while( true) { think(); Swait(chopstick[(I + 1)] % 5, chopstick[I]); eat(); Ssignal(chopstick[(I + 1)] % 5, chopstick[I]); } } |
方法2:利用信号量的保护机制实现。通过信号量mutex对eat()之前的取左侧和右侧筷
子的操作进行保护,使之成为一个原子操作,这样可以防止死锁的出现。
伪码:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
semaphore mutex =
1 ;
semaphore chopstick[ 5] = {1,1,1,1,1}; void philosopher( int I) { while( true) { think(); wait(mutex); wait(chopstick[(I + 1)] % 5); wait(chopstick[I]); signal(mutex); eat(); signal(chopstick[(I + 1)] % 5); signal(chopstick[I]); } } |
实际代码如下:
-
C++ Code1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <semaphore.h>
#include <pthread.h>
#include "apue.h"
#include "my_err.h"
#define N 5 // No. of philosopher
#define M 5 // times of eating
sem_t forks[N];
void *thr_philosopher( void *arg);
int main( int argc, char *argv[])
{
int i = 0;
int err;
pthread_t tid[N];
void *tret;
//initilize semaphore
for (i = 0; i < N; i++)
{
if(sem_init(&forks[i], 0, 1) != 0)
{
err_quit( "init forks error");
}
}
//create thread
for (i = 0; i < N; i++)
{
err = pthread_create(&tid[i], NULL, thr_philosopher, ( void *)i);
if (err != 0)
{
err_quit( "can't create thread %d: %s\n", i + 1, strerror(err));
}
}
//get the return value
for (i = 0; i < N; i++)
{
err = pthread_join(tid[i], &tret);
if (err != 0)
{
err_quit( "can't join with philosopher %d : %s\n", i + 1,
strerror(err));
}
printf( "-------------------philosopher %d has done-------------------\n", ( int)tret);
}
// delete the source of semaphore
for (i = 0; i < N; i++)
{
err = sem_destroy(&forks[i]);
if (err != 0)
{
err_sys( "can't destory semaphore");
}
}
exit( 0);
}
void *thr_philosopher( void *arg)
{
/*
* here cann't judge arg == NULL
* because (void *)0 will lead to arg = NULL
*/
int n = M;
int i = 0;
i = ( int)arg;
while ( n-- )
{
sleep( 1);
if ( i == N - 1)
{
sem_wait(&forks[ 0]);
sem_wait(&forks[i]);
}
else
{
sem_wait(&forks[i]);
sem_wait(&forks[i + 1]);
}
printf( "philosopher %d is eating\n", i + 1);
if ( i == N - 1)
{
sem_post(&forks[ 0]);
sem_post(&forks[i]);
}
else
{
sem_post(&forks[i]);
sem_post(&forks[i + 1]);
}
}
return (( void *)i);
}