文章目录
信号量
线程同步问题的另一种方式——信号量。
他与条件变量的作用一样,但是在使用的过程当中,信号量比条件变量更加简单,代码更加简洁。
条件变量操作时,往往需要判定是否满足阻塞线程的条件。但是信号量相当方便。
案例描述
消费者老王负责把车开出来;生产者老赵负责把车停进去。二者在工作时需要有条件判定,如果不满足条件,他们的进程就会阻塞。
在生成者工作时:只要车位足够,可以同时好几个线程同时工作;也可以一个线程工作,工作很多次。
在消费者工作时:只要车足够,可以同时好几个线程同时工作;也可以一个线程工作,工作很多次。
通过信号量来对二者的工作进行控制,首先要搞清楚信号量资源。
生产者(老赵)停进去一辆车,生产者(老赵)的信号量资源-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/