首先,我们来理清几个概念:
1.生产者消费者模型, 生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中添加产品,消费者从存储空间中取走产品,当存储空间为空时,消费者阻塞,当存储空间满时,生产者阻塞。
2.互斥量, 也就是互斥锁。互斥锁保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象,但互斥锁有不足的地方,所以出现了条件变量,下面我会举个例子进行说明。
3.条件变量(semaphore), 条件变量不是锁 。条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用。
4.信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。
理清了概念,现在来举例说明互斥锁的不足:
//假设一个链表来存储我们想要的数据Node
Node* head = NULL;
//一般情况下我们会判断链表是不是为空
while(head == NULL)
{
//如果我们想让代码在这里阻塞,等待链表中出现数据
//如果只使用互斥锁,那么做不到,你只能让它不断循环,等待链表中出现数据
continue;
}
//链表不为空处理代码
.......
现在应该很清楚了为什么要使用条件变量,上代码。
使用互斥量和条件变量实现生产者消费者模型(串行)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
/*
* 使用互斥量和条件变量实现生产者消费者模型
*/
typedef struct node
{
int data;
struct node* next;
}Node;
//永远指向链表头部的指针
Node *head = NULL;
//线程同步--互斥锁
pthread_mutex_t mutex;
//阻塞线程-- 条件变量
pthread_cond_t cond;
void* producer(void* arg)
{
while(1){
Node* pnew = (Node*)malloc(sizeof(Node));
pnew->data = rand() % 1000;
//使用互斥锁保护共享数据
pthread_mutex_lock(&mutex);
pnew->next = head;
head = pnew;
printf("----------producer %lu, %d----------\n",pthread_self(),pnew->data);
pthread_mutex_unlock(&mutex);
//通知阻塞的消费者线程,解除阻塞。
pthread_cond_signal(&cond);
sleep(rand() % 3);
}
return NULL;
}
void* customer(void* arg)
{
while(1){
pthread_mutex_lock(&mutex);
//判断链表是否为空
if(head == NULL){
//线程阻塞
//该函数会对互斥锁解锁
pthread_cond_wait(&cond,&mutex);
//continue;
}
Node* pdel = head;
head = head->next;
printf("----------customer %lu, %d----------\n",pthread_self(),pdel->data);
free(pdel);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main(void)
{
//init
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
pthread_t p1,p2;
//创建生产者线程
pthread_create(&p1,NULL,producer,NULL);
//创建消费者线程
pthread_create(&p2,NULL,customer,NULL);
//阻塞回收子线程
pthread_join(p1,NULL);
pthread_join(p2,NULL);
//destroy 销毁
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
使用信号量实现生产者消费者模型(并行)
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdlib.h>
/*
* 使用信号量实现生产者消费者模型
*/
sem_t producer_sem;
sem_t customer_sem;
typedef struct node
{
int data;
struct node* next;
}Node;
//永远指向链表头部的指针
Node *head = NULL;
void* producer(void* arg)
{
while(1)
{
sem_wait(&producer_sem);//producer_sem-- 生产一个就少一个资源
Node * node = (Node*)malloc(sizeof(Node));
node->data = rand() % 1000;
node->next = head;
head = node;
printf("+++++ 生产者:%lu, %d\n", pthread_self(), node->data);
sem_post(&customer_sem); // custom_sem++ 可以获得一个数据,就增加一个资源
sleep(rand()%5);
}
return NULL;
}
void* customer(void* arg)
{
while(1)
{
sem_wait(&customer_sem);// customer_sem-- == 0, 阻塞 等待++,然后获得资源就--
Node* del = head;
head = head->next;
printf("----- 消费者:%lu, %d\n", pthread_self(), del->data);
free(del);
sem_post(&producer_sem);//producer_sem++ 消费者获得资源之后,生产者增加资源
sleep(rand()%5);
}
return NULL;
}
int main()
{
pthread_t thid[4];
//init
//分配四个资源给生产者
sem_init(&producer_sem,0,4);
sem_init(&customer_sem,0,0);
//这里你可以只使用两个线程,一个消费一个生产,但是不能超过你之前初始化的资源大小
pthread_create(&thid[0], NULL, producer, NULL);
pthread_create(&thid[1], NULL, producer, NULL);
pthread_create(&thid[2], NULL, producer, NULL);
pthread_create(&thid[3], NULL, customer, NULL);
for(int i=0; i<4; ++i)
{
pthread_join(thid[i], NULL);
}
//销毁
sem_destroy(&producer_sem);
sem_destroy(&customer_sem);
return 0;
}
编译时记得加上动态库
gcc xxx.c -o xxx -lpthread
感谢点赞!