选用互斥锁mutex,如创建5个, pthread_mutex_t m[5];
模型抽象:
5个哲学家 --> 5个线程; 5支筷子 --> 5把互斥锁 int left(左手), right(右手)
5个哲学家使用相同的逻辑,可通用一个线程主函数,void *tfn(void *arg),使用参数来表示线程编号:int i = (int)arg;
哲学家线程根据编号知道自己是第几个哲学家,而后选定锁,锁住,吃饭。否则哲学家thinking。
A B C D E
5支筷子,在逻辑上形成环: 0 1 2 3 4 分别对应5个哲学家:
所以有:
if(i == 4)
left = i, right = 0;
else
left = i, right = i+1;
振荡:如果每个人都攥着自己左手的锁,尝试去拿右手锁,拿不到则将锁释放。过会儿五个人又同时再攥着左手锁尝试拿右手锁,依然拿不到。如此往复形成另外一种极端死锁的现象——振荡。
避免振荡现象:只需5个人中,任意一个人,拿锁的方向与其他人相逆即可(如:E,原来:左:4,右:0 现在:左:0, 右:4)。
所以以上if else语句应改为:
if(i == 4)
left = 0, right = i;
else
left = i, right = i+1;
而后, 首先应让哲学家尝试加左手锁:
while {
pthread_mutex_lock(&m[left]); 如果加锁成功,函数返回再加右手锁,
如果失败,应立即释放左手锁,等待。
若,左右手都加锁成功 --> 吃 --> 吃完 --> 释放锁(应先释放右手、再释放左手,是加锁顺序的逆序)
}
主线程(main)中,初始化5把锁,销毁5把锁,创建5个线程(并将i传递给线程主函数),回收5个线程。
#include
#include
#include
#include
pthread_mutex_t m[5];
void *tfn(void *arg)
{
int i, l, r;
srand(time(NULL));
i = (int)arg;
if (i == 4)
l = 0, r = i;
else
l = i; r = i+1;
while (1) {
pthread_mutex_lock(&m[l]);
if (pthread_mutex_trylock(&m[r]) == 0) {
printf("\t%c is eating \n", 'A'+i);
pthread_mutex_unlock(&m[r]);
}
pthread_mutex_unlock(&m[l]);
sleep(rand() % 5);
}
return NULL;
}
int main(void)
{
int i;
pthread_t tid[5];
for (i = 0; i < 5; i++)
pthread_mutex_init(&m[i], NULL);
for (i = 0; i < 5; i++)
pthread_create(&tid[i], NULL, tfn, (void *)i);
for (i = 0; i < 5; i++)
pthread_join(tid[i], NULL);
for (i = 0; i < 5; i++)
pthread_mutex_destroy(&m[i]);
return 0;
}
避免死锁的方法:
1. 当得不到所有所需资源时,放弃已经获得的资源,等待。
2. 保证资源的获取顺序,要求每个线程获取资源的顺序一致。如:A获取顺序1、2、3;B顺序应也是1、2、3。若B为3、2、1则易出现死锁现象。
相较于多线程需注意问题:
需注意如何共享信号量 (注意:坚决不能使用全局变量 sem_t s[5])
实现:
main函数中:
循环 sem_init(&s[i], 0, 1); 将信号量初值设为1,信号量变为互斥锁。
循环 sem_destroy(&s[i]);
循环 创建 5 个子进程。 if(i < 5) 中完成子进程的代码逻辑。
循环 回收 5 个子进程。
子进程中:
if(i == 4)
left = 0, right == 4;
else
left = i, right = i+1;
while (1) {
使用 sem_wait(&s[left]) 锁左手,尝试锁右手,若成功 --> 吃; 若不成功 --> 将左手锁释放。
吃完后, 先释放右手锁,再释放左手锁。
}
#include
#include
#include
#include
#include
#include
#include
int main(void)
{
int i;
pid_t pid;
sem_t *s;
s = mmap(NULL, sizeof(sem_t)*5, PROT_READ|PROT_WRITE,
MAP_SHARED|MAP_ANON, -1, 0);
if (s == MAP_FAILED) {
perror("fail to mmap");
exit(1);
}
for (i = 0; i < 5; i++)
sem_init(&s[i], 0, 1); //信号量初值制定为1,信号量,变成了互斥锁
for (i = 0; i < 5; i++)
if ((pid = fork()) == 0)
break;
if (i < 5) { //子进程
int l, r;
srand(time(NULL));
if (i == 4)
l = 0, r = 4;
else
l = i, r = i+1;
while (1) {
sem_wait(&s[l]);
if (sem_trywait(&s[r]) == 0) {
printf(" %c is eating\n", 'A'+i);
sem_post(&s[r]);
}
sem_post(&s[l]);
sleep(rand() % 5);
}
exit(0);
}
for (i = 0; i < 5; i++)
wait(NULL);
for (i = 0; i < 5; i++)
sem_destroy(&s[i]);
munmap(s, sizeof(sem_t)*5);
return 0;
}
【重点注意】:
直接将sem_t s[5]放在全局位置,试图用于子进程间共享是错误的!应将其定义放置与mmap共享映射区中。main中:
sem_t *s = mmap(NULL, sizeof(sem_t) * 5, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
使用方式:将s当成数组首地址看待,与使用数组s[5]没有差异。