Linux基础|多线程|信号量——生产者消费者模型

本文详细介绍了使用信号量解决线程同步问题的生产者消费者模型,包括资源数为1和资源数大于1两种情况下的实现,以及加锁机制确保线程安全。
摘要由CSDN通过智能技术生成

信号量

线程同步问题的另一种方式——信号量。

他与条件变量的作用一样,但是在使用的过程当中,信号量比条件变量更加简单,代码更加简洁。
条件变量操作时,往往需要判定是否满足阻塞线程的条件。但是信号量相当方便

案例描述

消费者老王负责把车开出来;生产者老赵负责把车停进去。二者在工作时需要有条件判定,如果不满足条件,他们的进程就会阻塞。
在生成者工作时:只要车位足够,可以同时好几个线程同时工作;也可以一个线程工作,工作很多次。
在消费者工作时:只要车足够,可以同时好几个线程同时工作;也可以一个线程工作,工作很多次。
通过信号量来对二者的工作进行控制,首先要搞清楚信号量资源。
生产者(老赵)停进去一辆车,生产者(老赵)的信号量资源-1,消费者(老王)的信号量资源+1。如果老赵的信号量资源减为0,生产者就会堵塞,等待消费者消费;消费者每开出来一辆车,信号量资源-1,生产者信号量资源+1。生产者就会恢复

图片来自爱编程的大丙

信号量资源数为1的生产者消费者模型

该情况下,能够同时工作的生产者消费者线程只有1个

头文件

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

定义信号量类型变量与初始化

要添加两个,一个给生产者用,一个给消费者用。

//生产者的信号量
sem_t sem_producer;
//消费者的信号量
sem_t sem_consumer;

int main(){
	//生产者
	sem_init(&sem_producer, 0, 1); //0表示线程同步,第三个参数是初始化的资源数
	//消费者->资源初始化为0,因为生产者还没生产呢,我们需要消费者启动就阻塞
	sem_init(&sem_consumer, 0, 0);
}

创建生产者消费者线程与回收线程

int main(){
	pthread_t t1[5], t2[5];
	//生产者线程
	for (int i = 0; i < 5; ++i){
		pthread_create(&t1[i], NULl, producer, NULL);
	}
	//消费者线程
	for (int i = 0; i < 5; ++i){
		pthread_create(&t2[i], NULl, consumer, NULL);
	}
	//回收线程资源
	for(int i = 0; i < 5; ++i){
		pthread_join(t1[i], NULL); //第二个参数主要是拿pthread_exit()传递出的数据
		pthread_join(t2[i], NULL);
	}
	sem_destroy(sem_consumer); //销毁信号量资源
	sem_destroy(sem_producer);
	return 0
}

生产者和消费者的任务(回调)函数

//定义链表
strcuct Node{
	int number;
	struct Node* next;
};
//创建头结点
struct Node* head = NULL;

//生产者回调函数
void* producer(void* arg){
	while(1){
		//检测生产者的资源
		sem_wait(&sem_producer);//如果判断资源数为零,所有生产者线程都会被阻塞到该行
		//创建新结点
		struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
		//init node
		newNode->num = rand() % 1000;
		newNode->next = head;
		head = newNode;
		printf("生产者,ID: %ld, number: %d\n", pthread_self(), newNode->number);
		sem_post(&sem_consumer);//通知消费者可以进行消费
		sleep(rand() % 3);
	}
	return NULL;
}

//消费者的回调函数
void* consumer(void* arg){
	while(1){
		//如果消费者资源为零,消费者线程会被阻塞到该行
		sem_wait(&sem_consumer);
		struct Node* node = head;
		printf("消费者,ID: %ld, number: %d\n", pthread_self(), node->number);
		head = head->next;
		free(node);
		//消费者消费完毕资源,要通知生产者继续生产
		sem_post(&sem_producer);
		sleep(rand() % 3);
	}
	return NULL;
}

信号量资源数大于1的生产者消费者模型

主要是在main函数中进行修改,下述代码表示可以有5个生产者同时运行。但是生产者同时运行就产生了一个新问题,我们一定要做线程同步才行,不然会非法操作内存的。所以仍然需要锁在访问代码时线程能够线性得执行

//生产者的信号量
sem_t sem_producer;
//消费者的信号量
sem_t sem_consumer;

int main(){
	//生产者
	sem_init(&sem_producer, 0, 5); //0表示线程同步,第三个参数是初始化的资源数
	//消费者->资源初始化为0,因为生产者还没生产呢,我们需要消费者启动就阻塞
	sem_init(&sem_consumer, 0, 0);
}

加锁的定义和初始化代码

本题中互斥锁、读写锁都可以。本文加互斥锁

//生产者的信号量
sem_t sem_producer;
//消费者的信号量
sem_t sem_consumer;

pthread_mutex_t mutex;

int main(){
	//生产者
	sem_init(&sem_producer, 0, 5); //0表示线程同步,第三个参数是初始化的资源数
	//消费者->资源初始化为0,因为生产者还没生产呢,我们需要消费者启动就阻塞
	sem_init(&sem_consumer, 0, 0);

	pthread_mutex_init(&mutex, NULL);
	.
	.
	.
	pthread_mutex_destroy(&mutex);
	sem_destroy(sem_consumer); //销毁信号量资源
	sem_destroy(sem_producer);
	return 0
}

找到生产者和消费者临界区加锁

我们的临界区在哪呢,锁是加到sem_wait()上方还是下方呢?
如果我们生产者和消费者的锁都加在了sem_wait()上方,那么会造成死锁,可以写代码模拟一下即可。

//生产者回调函数
void* producer(void* arg){
	while(1){
		//检测生产者的资源
		sem_wait(&sem_producer);//如果判断资源数为零,所有生产者线程都会被阻塞到该行
		pthread_mutex_lock(&mutex);
		//创建新结点
		struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
		//init node
		newNode->num = rand() % 1000;
		newNode->next = head;
		head = newNode;
		printf("生产者,ID: %ld, number: %d\n", pthread_self(), newNode->number);
		pthread_mutex_unlock(&mutex);
		sem_post(&sem_consumer);//通知消费者可以进行消费
		
		sleep(rand() % 3);
	}
	return NULL;
}

//消费者的回调函数
void* consumer(void* arg){
	while(1){
		//如果消费者资源为零,消费者线程会被阻塞到该行
		sem_wait(&sem_consumer);
		pthread_mutex_lock(&mutex);
		struct Node* node = head;
		printf("消费者,ID: %ld, number: %d\n", pthread_self(), node->number);
		head = head->next;
		free(node);
		pthread_mutex_unlock(&mutex);
		//消费者消费完毕资源,要通知生产者继续生产
		sem_post(&sem_producer);
		sleep(rand() % 3);
	}
	return NULL;
}

致谢:
爱编程的大丙 苏丙榅 https://subingwen.cn/linux/thread-sync/

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值