哲学家用餐模型分析


多线程版:

    选用互斥锁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个哲学家:

哲学家用餐模型分析_第1张图片

    所以有:

     

        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]没有差异。

你可能感兴趣的:(Linux应用编程)