一、使用互斥锁和条件变量实现
三种思路都可以,推荐第1种简单好记:
1、万金油思路,生产者和消费者都有条件等待和唤醒
生产者拿到互斥锁后,检查缓冲区的产品数量:
如果缓冲区是满的,则生产者条件等待
如果缓冲区不是满的,就生产,然后发送信号唤醒消费者消费,释放互斥锁
消费者拿到互斥锁后,检查缓冲区的产品数量:
如果缓冲区是空的,则消费者条件等待
如果缓冲区不是空的,就消费,然后发送信号唤醒生产者生产,释放互斥锁
2、只有消费者条件等待,生产者负责唤醒
生产者拿到互斥锁后,检查缓冲区的产品数量:
如果缓冲区不是满的,就生产,然后发送信号唤醒消费者消费,释放互斥锁
消费者拿到互斥锁后,检查缓冲区的产品数量:
如果缓冲区是空的,则消费者条件等待
如果缓冲区不是空的,就消费,释放互斥锁
3、只有生产者条件等待,消费者负责唤醒
生产者拿到互斥锁后,检查缓冲区的产品数量:
如果缓冲区是满的,则生产者条件等待
如果缓冲区不是满的,就生产,释放互斥锁
消费者拿到互斥锁后,检查缓冲区的产品数量:
如果缓冲区不是空的,就消费,然后发送信号唤醒生产者生产,释放互斥锁
第1种的代码实现
第2,3两种是子集,只需要去除对应代码即可
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
// 生产者消费者的数量
#define CONSUMER 5
#define PRODUCER 5
// 缓冲区最大数目
#define BUFFER_SIZE 10
pthread_mutex_t g_mutex;
pthread_cond_t g_cond;
typedef struct st_buffer_t {
int buf[BUFFER_SIZE]; // 缓冲区
int count; // 剩余产品的数量
int in; // 生产者放置产品的位置
int out; // 消费者取产品的位置
} buffer_t;
buffer_t g_buff = {{0}, 0, 0, 0};
// 打印缓冲情况
void print()
{
for (int i = 0; i < BUFFER_SIZE; i++) {
printf("%d ", g_buff.buf[i]);
}
printf("\n");
}
void* producer(void *arg)
{
int producer_id = *(int *)arg;
printf("Producer %d starting.\n", producer_id);
while (1) {
// 用sleep可以降低生产和消费的速度,便于观察
sleep(1);
pthread_mutex_lock(&g_mutex);
// 加上while防止线程错误唤醒
while (g_buff.count == BUFFER_SIZE) {
// 走到这里wait时会把互斥锁释放掉
pthread_cond_wait(&g_cond, &g_mutex);
}
// 生产
g_buff.in %= BUFFER_SIZE;
g_buff.buf[g_buff.in] = 1;
printf("Prodcuer %d produce 1 in %d: ", producer_id, g_buff.in);
print();
g_buff.in++;
g_buff.count++;
// 生产后发送信号唤醒消费者线程
pthread_cond_signal(&g_cond);
pthread_mutex_unlock(&g_mutex);
}
return NULL;
}
void* consumer(void *arg)
{
int consumer_id = *(int *)arg;
printf("Consumer %d starting.\n", consumer_id);
while (1) {
// 用sleep可以降低生产和消费的速度,便于观察
sleep(1);
pthread_mutex_lock(&g_mutex);
// 加上while防止线程错误唤醒
while (g_buff.count == 0) {
// 走到这里wait时会把互斥锁释放掉
pthread_cond_wait(&g_cond, &g_mutex);
}
// 消费
g_buff.out %= BUFFER_SIZE;
printf("Consumer %d consume %d in %d: ", consumer_id, g_buff.buf[g_buff.out], g_buff.out);
g_buff.buf[g_buff.out] = 0;
print();
g_buff.out++;
g_buff.count--;
// 消费后发送信号唤醒生产者线程
pthread_cond_signal(&g_cond);
pthread_mutex_unlock(&g_mutex);
}
return NULL;
}
int main(int argc, char *argv[])
{
// 初始化互斥锁和条件变量
pthread_mutex_init(&g_mutex, NULL);
pthread_cond_init(&g_cond, NULL);
pthread_t consumers[CONSUMER];
pthread_t producers[PRODUCER];
int consumers_id[CONSUMER];
int producers_id[PRODUCER];
// 创建生产者和消费者的线程
for (int i = 0, j = 0; i < CONSUMER || j < PRODUCER; i++, j++) {
if (j < PRODUCER) {
producers_id[j] = j;
pthread_create(&producers[j], NULL, producer, (void *)&producers_id[j]);
}
if (i < CONSUMER) {
consumers_id[i] = i;
pthread_create(&consumers[i], NULL, consumer, (void *)&consumers_id[i]);
}
}
for (int i = 0, j = 0; i < CONSUMER || j < PRODUCER; i++, j++) {
if (j < PRODUCER) {
pthread_join(producers[j], NULL);
}
if (i < CONSUMER) {
pthread_join(consumers[i], NULL);
}
}
// 销毁互斥锁和条件变量
pthread_mutex_destroy(&g_mutex);
pthread_cond_destroy(&g_cond);
return 0;
}
二、使用互斥锁和同步信号量实现
思路:
两个信号量,一个empty表示可生产产品的数量,一个full表示已生产产品的数量
生产者wait empty减一后,拿到互斥锁,生产,释放互斥锁,post full加一
消费者wait full减一后,拿到互斥锁,消费,释放互斥锁,post empty加一
PS:一般推荐两个保持先同步再互斥,这样可以使得互斥区代码更短,有效提高并发性。
代码实现
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
// 生产者消费者的数量
#define CONSUMER 5
#define PRODUCER 5
// 缓冲区最大数目
#define BUFFER_SIZE 10
pthread_mutex_t g_mutex;
sem_t g_empty_sem;
sem_t g_full_sem;
typedef struct st_buffer_t {
int buf[BUFFER_SIZE]; // 缓冲区
int count; // 剩余产品的数量
int in; // 生产者放置产品的位置
int out; // 消费者取产品的位置
} buffer_t;
buffer_t g_buff = {{0}, 0, 0, 0};
// 打印缓冲情况
void print()
{
for (int i = 0; i < BUFFER_SIZE; i++) {
printf("%d ", g_buff.buf[i]);
}
printf("\n");
}
void* producer(void *arg)
{
int producer_id = *(int *)arg;
printf("Producer %d starting.\n", producer_id);
while (1) {
// 用sleep可以降低生产和消费的速度,便于观察
sleep(1);
// 等待empty信号量大于0
sem_wait(&g_empty_sem);
pthread_mutex_lock(&g_mutex);
// 生产
g_buff.in %= BUFFER_SIZE;
g_buff.buf[g_buff.in] = 1;
printf("Prodcuer %d produce 1 in %d: ", producer_id, g_buff.in);
print();
g_buff.in++;
g_buff.count++;
pthread_mutex_unlock(&g_mutex);
// 增加full信号量的值
sem_post(&g_full_sem);
}
return NULL;
}
void* consumer(void *arg)
{
int consumer_id = *(int *)arg;
printf("Consumer %d starting.\n", consumer_id);
while (1) {
// 用sleep可以降低生产和消费的速度,便于观察
sleep(1);
// 等待empty信号量大于0
sem_wait(&g_full_sem);
pthread_mutex_lock(&g_mutex);
// 消费
g_buff.out %= BUFFER_SIZE;
// 可以通过g_buff.buf[g_buff.out]的值来判断消费者是否消费对了,该值必须为1
printf("Consumer %d consume %d in %d: ", consumer_id, g_buff.buf[g_buff.out], g_buff.out);
g_buff.buf[g_buff.out] = 0;
print();
g_buff.out++;
g_buff.count--;
pthread_mutex_unlock(&g_mutex);
// 增加empty信号量的值
sem_post(&g_empty_sem);
}
return NULL;
}
int main(int argc, char *argv[])
{
// 初始化互斥锁和同步信号量
// empty表示可生产的数量;full表示已生产的数量
pthread_mutex_init(&g_mutex, NULL);
sem_init(&g_empty_sem, 0, BUFFER_SIZE);
sem_init(&g_full_sem, 0, 0);
pthread_t consumers[CONSUMER];
pthread_t producers[PRODUCER];
int consumers_id[CONSUMER];
int producers_id[PRODUCER];
// 创建生产者和消费者的线程
for (int i = 0, j = 0; i < CONSUMER || j < PRODUCER; i++, j++) {
if (j < PRODUCER) {
producers_id[j] = j;
pthread_create(&producers[j], NULL, producer, (void *)&producers_id[j]);
}
if (i < CONSUMER) {
consumers_id[i] = i;
pthread_create(&consumers[i], NULL, consumer, (void *)&consumers_id[i]);
}
}
for (int i = 0, j = 0; i < CONSUMER || j < PRODUCER; i++, j++) {
if (j < PRODUCER) {
pthread_join(producers[j], NULL);
}
if (i < CONSUMER) {
pthread_join(consumers[i], NULL);
}
}
// 销毁互斥锁和条件变量
pthread_mutex_destroy(&g_mutex);
sem_destroy(&g_empty_sem);
sem_destroy(&g_full_sem);
return 0;
}