多线程经典案例

一. 生产者消费者问题

生产者消费者共享缓冲区,生产者向缓冲区中放数据,消费者从缓冲取中取数据,当缓冲区中被放满时,生产者进程就必须进入挂起状态,直到消费者从缓冲中取走数据时,生产者才能继续向缓冲区中存放数据,同样当缓冲取中没有数据时,消费者进程就必须进入挂起休眠状态,直到生产者向缓冲区中放入数据时,消费者才能被唤醒继续从缓冲区中取走数据。
生产者消费者问题,也称有限缓冲问题,是一个多线程同步问题的经典案例。

互斥锁和条件变量配合使用达到多个线程对共享区域的读写同步。

基本流程:
生产者加锁生产数据之后解锁并调用pthread_cond_signal()唤醒消费者消费;
消费者加锁然后调用pthread_cond_wait()阻塞等待被唤醒,被唤醒之后自动加锁进行消费。


需要注意几点:
1.对共享区域的访问即读/写等任何操作都需要先加锁
2.为什么需要条件变量?
多消费者会竞争
3.pthread_cond_signal()可能唤醒多个消费者带来竞争问题,需要使用while来进行条件判断,以保证临界区内只有一个线程在处理

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

#define productsMAX 5	

typedef struct Node
{
	int ppp;
	struct Node* next;
}node;

int num;
node *head = NULL;
node *tail = NULL;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *producter1()
{
	while(1)
	{
		if(num < productsMAX)
		{
			pthread_mutex_lock(&mutex);

			tail = (node *)malloc(sizeof(node));
			tail->ppp = rand() % 400; 
			tail->next = head;
			head = tail;
			num++;

			printf("producter1 : %d\n", tail->ppp);

			pthread_mutex_unlock(&mutex);

			pthread_cond_signal(&cond);

		}
		else printf("maxmax1\n");

		sleep(rand() % 2);
	}
}

void *producter2()
{
	while(1)
	{
		if(num < productsMAX)
		{
			pthread_mutex_lock(&mutex);

			tail = (node *)malloc(sizeof(node));
			tail->ppp = rand() % 400; 
			tail->next = head;
			head = tail;
			num++;

			printf("producter2 : %d\n", tail->ppp);

			pthread_mutex_unlock(&mutex);

			pthread_cond_signal(&cond);

		}
		else printf("maxmax2\n");
		sleep(rand() % 2);
	}
}

void *consumer1()
{
	while(1)
		{
			pthread_mutex_lock(&mutex);
			while(head == NULL)
				{
					printf("minmin1\n");
					pthread_cond_wait(&cond, &mutex);
				}
			tail = head;
			head = tail->next;

			printf("--------------------consumer1 : %d\n", tail->ppp);
			free(tail);
			tail = NULL;
			num--;

			pthread_mutex_unlock(&mutex);

			sleep(rand() % 3);
		}
}

void *consumer2()
{
	while(1)
		{
			pthread_mutex_lock(&mutex);
			while(head == NULL)
				{
					printf("minmin2\n");
					pthread_cond_wait(&cond, &mutex);
				}
			tail = head;
			head = tail->next;

			printf("-----------------------------------consumer2 : %d\n", tail->ppp);
			free(tail);
			tail = NULL;
			num--;

			pthread_mutex_unlock(&mutex);

			sleep(rand() % 3);
		}
}

int main()
{
	pthread_t tid1, tid2, tid3, tid4;
	pthread_create(&tid1, NULL, producter1, NULL);
	pthread_create(&tid2, NULL, producter2, NULL);
	pthread_create(&tid3, NULL, consumer1, NULL);
	pthread_create(&tid4, NULL, consumer2, NULL);

	pthread_join(tid1, NULL);
	pthread_join(tid2, NULL);
	pthread_join(tid3, NULL);
	pthread_join(tid4, NULL);
	return 0;
}

二.哲学家问题

有五个哲学家绕着圆桌坐,每个哲学家面前有一盘面,两人之间有一支筷子,这样每个哲学家左右各有一支筷子。哲学家有2个状态,思考或者拿起筷子吃饭。如果哲学家拿到一只筷子,不能吃饭,直到拿到2只才能吃饭,并且一次只能拿起身边的一支筷子。一旦拿起便不会放下筷子直到把饭吃完,此时才把这双筷子放回原处。如果,很不幸地,每个哲学家拿起他或她左边的筷子,那么就没有人可以吃到饭了。
哲学家进餐问题是一个多线程运用的经典例子,涉及到线程同步/互斥,临界区访问问题以及死锁问题。

共享数据就是五根筷子,可以加五个锁或者使用信号量来解决共享问题。


互斥锁

pthread_mutex_t chopstick[5] ;
for (int i = 0; i < 5; i++)
       pthread_mutex_init(&chopstick[i],NULL); 

void *eat(void *arg)
{
	int id = *(int *)arg;
	
	pthread_mutex_lock(&chopstick[id-1]); 	//第i个哲学家拿到左边筷子
	pthread_mutex_lock(&chopstick[id%5]);	//拿到右边筷子
	sleep(5); //吃饭
	pthread_mutex_unlock(&chopstick[id-1]);	//第i个哲学家放下左边筷子
	pthread_mutex_unlock(&chopstick[id%5]);	//放下右边筷子
}

信号量

sem_t mutex[5];

for(int i = 0; i < 5; i++)
        sem_init(&sem[i], 0, 1);

void *eat(void *arg)
{
	int id = *(int *)arg;
	
	sem_wait(&sem[id-1]);	//第i个哲学家拿到左边筷子
	sem_wait(&sem[id%5]);	//拿到右边筷子
	sleep(5); //吃饭
	sem_post(&sem[id-1]);	//第i个哲学家放下左边筷子
	sem_post(&sem[id%5);	//放下右边筷子
}

for(int i = 0; i < 5; i++)
        sem_destroy(&sem[i]);

但这种方法中五个哲学家可能同时拿到左筷子,就会导致所有人都吃不上饭即死锁问题。


解决方法
1.拿筷子时要么全拿,要么全不拿
利用AND型信号


2.多加信号量来保证最多四个人同时拿左筷子最少一个人能吃上。

信号量

sem_t sem[6];

for(int i = 0; i < 5; i++)
	sem_init(&sem[i], 0, 1);                       
sem_init(&sem[5], 0, 4);	//最多四个人同时拿左筷子

void *eat_or_think(void *arg)
{
    int id = *(int *)arg;

    sem_wait(&sem[5]);
	sem_wait(&sem[id-1]); 	//第i个哲学家拿到左边筷子
    printf("%d get left(%d)\n", id, id-1);
	sem_wait(&sem[id%5]);		//拿到右边筷子
    printf("-----------%d is eating(%d)\n",id, id%5);
	sleep(5); //吃饭
	sem_post(&sem[id-1]);	//第i个哲学家放下左边筷子
	sem_post(&sem[id%5]);	//放下右边筷子
    printf("********************%d ate(%d)\n",id, id%5);
    sem_post(&sem[5]);
}

for(int i = 0; i < 6; i++)
        sem_destroy(&sem[i]);

3.奇数号哲学家先拿左筷子,偶数号哲学家先拿右筷子。

互斥锁

pthread_mutex_t chopstick[5] ;

void *eat_or_think(void *arg)
{
    int id = *(int *)arg;

    if(id&1)
    {
   		pthread_mutex_lock(&chopstick[id-1]); 	
        printf("%d get left(%d)\n", id, id-1);
		pthread_mutex_lock(&chopstick[id%5]);	
        printf("-----------%d is eating(%d)\n",id, id%5);
    }
    else
    {
        pthread_mutex_lock(&chopstick[id%5]); 
        printf("%d get right(%d)\n", id, id%5);
		pthread_mutex_lock(&chopstick[id-1]);	
        printf("-----------%d is eating(%d)\n",id, id-1);
    }
		sleep(5); //吃饭
		pthread_mutex_unlock(&chopstick[id-1]);	//放下左边筷子
		pthread_mutex_unlock(&chopstick[id%5]);	//放下右边筷子
        printf("********************%d ate\n",id);
}

信号量

sem_t sem[5];

void *eat_or_think(void *arg)
{
    int id = *(int *)arg;

    if(id&1)
    {
		sem_wait(&sem[id-1]); 
        printf("%d get left(%d)\n", id, id-1);
		sem_wait(&sem[id%5]);
        printf("-----------%d is eating(%d)\n",id, id%5);
    }		
    else
    {
        sem_wait(&sem[id%5]); 
        printf("%d get right(%d)\n", id, id%5);
		sem_wait(&sem[id-1]);
        printf("-----------%d is eating(%d)\n",id, id-1);
    }
        
		sleep(5); //吃饭
		sem_post(&sem[id-1]);	//第i个哲学家放下左边筷子
		sem_post(&sem[id%5]);	//放下右边筷子
        printf("********************%d ate(%d)\n",id, id%5);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值