一. 生产者消费者问题
生产者消费者共享缓冲区,生产者向缓冲区中放数据,消费者从缓冲取中取数据,当缓冲区中被放满时,生产者进程就必须进入挂起状态,直到消费者从缓冲中取走数据时,生产者才能继续向缓冲区中存放数据,同样当缓冲取中没有数据时,消费者进程就必须进入挂起休眠状态,直到生产者向缓冲区中放入数据时,消费者才能被唤醒继续从缓冲区中取走数据。
生产者消费者问题,也称有限缓冲问题,是一个多线程同步问题的经典案例。
互斥锁和条件变量配合使用达到多个线程对共享区域的读写同步。
基本流程:
生产者加锁生产数据之后解锁并调用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.当一个人拿到左筷子后判断是否能拿到右筷子,如果拿不到右筷子就放下左筷子。
互斥锁
pthread_mutex_trylock()加不上锁时不阻塞直接返回EBUSY
pthread_mutex_t chopstick[5] ;
void *eat_or_think(void *arg)
{
int id = *(int *)arg;
while(1)
{
pthread_mutex_lock(&chopstick[id-1]); //第i个哲学家拿到左边筷子
printf("%d get left(%d)\n", id, id-1);
if(pthread_mutex_trylock(&chopstick[id%5]) == EBUSY) //not get right
{
pthread_mutex_unlock(&chopstick[id-1]);
printf("%d give left(%d)\n", id, id-1);
sleep(3);
continue;
}
printf("-----------%d is eating(%d)\n",id, id%5); //拿到右边筷子
sleep(5); //吃饭
pthread_mutex_unlock(&chopstick[id-1]); //第i个哲学家放下左边筷子
pthread_mutex_unlock(&chopstick[id%5]); //放下右边筷子
printf("********************%d ate(%d)\n",id, id%5);
break;
}
}
信号量
sem_trywait()遇到信号量为0时不阻塞直接返回不为0的任何值
sem_t sem[5];
for(int i = 0; i < 5; i++)
sem_init(&sem[i], 0, 1);
void *eat_or_think(void *arg)
{
int id = *(int *)arg;
while(1)
{
sem_wait(&sem[id-1]); //第i个哲学家拿到左边筷子
printf("%d get left(%d)\n", id, id-1);
if(sem_trywait(&sem[id%5]) != 0)
{
sem_post(&sem[id-1]);
printf("%d give left(%d)\n", id, id-1);
sleep(3);
continue;
}
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);
break;
}
}
for(int i = 0; i < 5; i++)
sem_destroy(&sem[i]);
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);
}