基础知识:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
关于mutex更多详细内容可以参考博客:
https://book.itheima.net/course/223/1277519158031949826/1277528625427521539
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
关于semaphore.h头文件以及关于信号量的函数,可以参考博客:
https://www.cnblogs.com/aaronwxb/archive/2012/06/12/2546412.html
一、 生产者消费者问题
生产者消费者共享缓冲区,生产者向缓冲区中放数据,消费者从缓冲取中取数据,当缓冲区中被放满时,生产者进程就必须进入挂起状态,直到消费者从缓冲中取走数据时,生产者才能继续向缓冲区中存放数据,同样当缓冲取中没有数据时,消费者进程就必须进入挂起休眠状态,直到生产者向缓冲区中放入数据时,消费者才能被唤醒继续从缓冲区中取走数据。
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这种生产消费能力不均衡的问题,所以便有了生产者和消费者模式。
————————————————
原文链接:https://blog.csdn.net/weixin_41143631/article/details/89442468
1. 单生产者,单消费者
有两个进程:一组生产者进程和一组消费者进程共享一个初始为空、固定大小为n的缓存(缓冲区)。生产者的工作是制造一段数据,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待,如此反复; 同时,只有缓冲区不空时,消费者才能从中取出消息,一次消费一段数据(即将其从缓存中移出),否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。
问题的关键在于两点:
- 要保证生产者在缓存区满时不再向其中加入资源
- 要保证消费者不从一个空的缓冲区中获取资源
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
sem_t empty, full; //全局同步信号量 empty表示缓冲区空余量 full表示缓冲区数据量
pthread_mutex_t mutex; //全局互斥变量
int buffer_count = 0; //表示管道内的产品数目
void *producer(void *arg);
void *consumer(void *arg);
int main()
{
pthread_t thrd_prod, thrd_cons;
//初始化
pthread_mutex_init(&mutex, NULL);
sem_init(&empty, 0, 5);
sem_init(&full, 0, 0);
//创建生产者和消费者线程
if (pthread_create(&thrd_prod, NULL, (void *)producer, NULL) != 0)
{
printf("thread create failed.\n");
exit(1);
}
if (pthread_create(&thrd_cons, NULL, (void *)consumer, NULL) != 0)
{
printf("thread create failed.\n");
exit(1);
}
//等待线程结束
if (pthread_join(thrd_prod, NULL) != 0)
{
printf("thread wait failed.\n");
exit(1);
}
if (pthread_join(thrd_cons, NULL) != 0)
{
printf("thread wait failed.\n");
exit(1);
}
sem_destroy(&empty);
sem_destroy(&full);
pthread_mutex_destroy(&mutex);
return 0;
}
void *producer(void *arg)
{
while (1)
{
//当没有缓冲区没有空位置(即empty==0)时,sem_wait()等待,直到信号量不为0
sem_wait(&empty); //empty-1
pthread_mutex_lock(&mutex); //加锁
//成功占有互斥量,接下来可以对缓冲区(仓库)进行生产
buffer_count++;
printf("producer put a product to buffer. the buffer count is %d\n", buffer_count);
pthread_mutex_unlock(&mutex); //解锁
sem_post(&full); //full+1
}
}
void *consumer(void *arg)
{
while(1)
{
sem_wait(&full);
pthread_mutex_lock(&mutex);
buffer_count--;
printf("consumer get a product from buffer. the buffer count is %d\n", buffer_count);
pthread_mutex_unlock(&mutex);
sem_post(&empty);
}
}
2. 多生产者,多消费者
桌子上有一个盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等吃盘子中的橘子,女儿专等吃盘子中的苹果。只有盘子为空时,爸爸或妈妈才可向盘子中放一个水果;仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出。
#include<stdio.h>
#include<stdlib.h>
#include<semaphore.h>
#include<pthread.h>
#include<unistd.h>
sem_t apple, orange, empty;
int count;
void *mom(void *arg);
void *pap(void *arg);
void *dau(void *arg);
void *son(void *arg);
int main()
{
pthread_t thrd_mom, thrd_pap, thrd_dau, thrd_son;
//初始化
sem_init(&apple, 0, 0);
sem_init(&orange, 0, 0);
sem_init(&empty, 0, 1);
//创建线程
if(pthread_create(&thrd_mom, NULL, (void *)mom, NULL) != 0)
{
printf("thread create failed.\n");
exit(1);
}
if(pthread_create(&thrd_pap, NULL, (void *)pap, NULL) != 0)
{
printf("thread create failed.\n");
exit(1);
}
if(pthread_create(&thrd_dau, NULL, (void *)dau, NULL) != 0)
{
printf("thread create failed.\n");
exit(1);
}
if(pthread_create(&thrd_son, NULL, (void *)son, NULL) != 0)
{
printf("thread create failed.\n");
exit(1);
}
//等待线程结束
if(pthread_join(thrd_mom, NULL) != 0)
{
printf("thread wait failed.\n");
exit(1);
}
if(pthread_join(thrd_pap, NULL) != 0)
{
printf("thread wait failed.\n");
exit(1);
}
if(pthread_join(thrd_dau, NULL) != 0)
{
printf("thread wait failed.\n");
exit(1);
}
if(pthread_join(thrd_son, NULL) != 0)
{
printf("thread wait failed.\n");
exit(1);
}
sem_destroy(&apple);
sem_destroy(&orange);
sem_destroy(&empty);
return 0;
}
void *mom(void *arg)
{
while(1)
{
sem_wait(&empty);
printf("mom put an orange.\n");
printf("mom --> %d", count);
count++;
sem_post(&orange);
sleep(random() % 2);
}
}
void *pap(void *arg)
{
while(1)
{
sem_wait(&empty);
printf("pap put an apple.\n");
printf("pap --> %d", count);
count++;
sem_post(&apple);
sleep(random() % 2);
}
}
void *dau(void *arg)
{
while(1)
{
sem_wait(&apple);
printf("daughter get an apple.\n");
printf("daughter <-- %d", count);
count--;
sem_post(&empty);
sleep(random() % 2);
}
}
void *son(void *arg)
{
while(1)
{
sem_wait(&orange);
printf("son get an orange.\n");
printf("son <-- %d", count);
count--;
sem_post(&empty);
sleep(random() % 2);
}
}
二、 哲学家问题
有五个哲学家绕着圆桌坐,每个哲学家面前有一盘面,两人之间有一支筷子,这样每个哲学家左右各有一支筷子。哲学家有2个状态,思考或者拿起筷子吃饭。如果哲学家拿到一只筷子,不能吃饭,直到拿到2只才能吃饭,并且一次只能拿起身边的一支筷子。一旦拿起便不会放下筷子直到把饭吃完,此时才把这双筷子放回原处。如果,很不幸地,每个哲学家拿起他或她左边的筷子,那么就没有人可以吃到饭了。
哲学家进餐问题是一个多线程运用的经典例子,涉及到线程同步/互斥,临界区访问问题以及死锁问题。
哲学家问题的难点在于如何避免上述加粗字体的问题。本文采取两种方法解决这个问题。
1. 法一
一人开始拿筷子的时候,其他人不可以拿筷子
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#define N 5
//某位哲学家开始拿第一支筷子时,其他哲学家全部不准拿筷子(即使他已经饿了),
//只可以放下,直到这位哲学家拿到一双筷子之后,允许其他筷子被拿起
sem_t chopsticks[N]; //筷子的编号
pthread_mutex_t mutex; //定义互斥锁
int philosophers[N] = {
0, 1, 2, 3, 4}; //哲学家的编号
void *philosopher(void *arg);
void delay(int len);
int main()
{
pthread_t philo[N];
srand(time(NULL));
//初始化信号量
for (int i = 0; i < N; i++)
{
sem_init(&chopsticks[i], 0, 1);
}
//初始化互斥锁
pthread_mutex_init(&mutex, NULL);
//创建线程
for (int i = 0; i < N; i++)
{
pthread_create(&philo[i], NULL, (void *)philosopher, &philosophers[i]);
}
//挂起线程
for (int i = 0; i < N; i++)
{
pthread_join(philo[i], NULL);
}
//销毁信号量
for (int i = 0; i < N; i++)
{
sem_destroy(&chopsticks[i]);
}
//销毁互斥锁
for (int i = 0; i < N; i++)
{
pthread_mutex_destroy(&mutex);
}
return 0;
}
void *philosopher(void *arg)
{
int i = *(int *)arg;
int left = i; //左筷子的编号和哲学家的编号相同
int right = (i + 1) % N;
while (1)
{
printf("Philosophy %d is thinking.\n", i);
delay(60000);
printf("Philosophy %d is hungry.\n", i);
//加锁
pthread_mutex_lock(&mutex);
sem_wait(&chopsticks[left]); //当这个哲学家的左筷子存在时,可以继续,否则等待
printf("Now philosophy %d had chopstick %d, He have only one, Can Not Eat!\n", i, left);
sem_wait(&chopsticks[right]);
printf("philosophy %d had chopstick %d, He have a pair of chopsticks, He Can Eat!\n", i, right);
//解锁
pthread_mutex_unlock(&mutex);
printf("philosophy %d begin eating!\n", i);
delay(60000);
sem_post(&chopsticks[left]);
sem_post(&chopsticks[right]);
printf("phylosophy %d lay down chopstick %d & %d\n", i, left, right);
}
}
void delay(int len)
{
int i = rand() % len;
int x;
while (i > 0)
{
x = rand() % len;
while (x > 0)
{
x--;
}
i--;
}
}
2. 法二
规定奇数号哲学家先拿左筷子,再拿右筷子;偶数号哲学家相反。这样就可以保证每次至少有一个人可以拿到两根筷子。
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<time.h>
#define N 5
void delay(int len);
void *philosopher(void *arg);
//规定奇数号哲学家先拿左筷子再拿右筷子,而偶数号哲学家相反。
//所以将是 2,3 号哲学家竞争 3 号筷子,4,5 号哲学家竞争 5 号筷子。
sem_t chopsticks[N]; //定义5种资源
int philosophers[N] = {
0, 1, 2, 3, 4}; //定义5位哲学家的编号
//规定编号奇数的哲学家先拿左手边的筷子
int main()
{
pthread_t philo[N];
srand(time(NULL));
//初始化信号量
for(int i=0; i<N; i++)
{
sem_init(&chopsticks[i], 0, 1);
}
//创建线程
for(int i=0; i<N; i++)
{
pthread_create(&philo[i], NULL, (void *)philosopher, &philosophers[i]);
}
//挂起线程
for(int i=0; i<N; i++)
{
pthread_join(philo[i], NULL);
}
//销毁信号量
for(int i=0; i<N; i++)
{
sem_destroy(&chopsticks[i]);
}
return 0;
}
void *philosopher(void *arg)
{
int i = *(int *)arg;
int left = i;
int right = (i+1) % N;
while(1)
{
printf("philosophy %d is thinking.\n", i);
delay(60000);
printf("philosophy %d is hungry.\n", i);
if(i % 2 != 0) //编号为奇数
{
sem_wait(&chopsticks[left]);
printf("Now philosophy %d had chopstick %d, He have only one, Can Not Eat!\n", i, left);
sem_wait(&chopsticks[right]);
printf("philosophy %d had chopstick %d, Now he have a pair of chopsticks, He Can Eat!\n", i, right);
printf("philosophy %d begin eating!\n", i);
delay(60000);
sem_post(&chopsticks[left]);
sem_post(&chopsticks[right]);
printf("philosophy %d lay down chopsticks %d & %d", i, left, right);
}
else
{
sem_wait(&chopsticks[right]);
printf("Now philosophy %d had chopstick %d, He have only one, Can Not Eat!\n", i, right);
sem_wait(&chopsticks[left]);
printf("philosophy %d had chopstick %d, Now he have a pair of chopsticks, He Can Eat!\n", i, left);
printf("philosophy %d begin eating!\n", i);
delay(60000);
sem_post(&chopsticks[left]);
sem_post(&chopsticks[right]);
printf("philosophy %d lay down chopsticks %d & %d", i, left, right);
}
}
}
void delay(int len)
{
int i = rand() % len;
int x;
while (i > 0)
{
x = rand() % len;
while (x > 0)
{
x--;
}
i--;
}
}