当前位置: 首页 > news >正文

【Linux】信号量

Alt

🔥个人主页Quitecoder

🔥专栏linux笔记仓

Alt

目录

    • 01.信号量
      • 环形队列的生产消费模型

01.信号量

信号量(Semaphore) 是一种用于管理多个线程对共享资源访问的同步机制

它的本质是一个整型计数器,表示可用资源的数量

在这里插入图片描述

int sem_init(sem_t *sem, int pshared, unsigned int value);
参数类型说明
semsem_t*指向信号量变量的指针(必须先声明
psharedint是否在进程间共享信号量
valueunsigned int信号量初始值

pshared:

  • 0:表示线程共享(即线程间共享该信号量,使用于同一进程中的线程,常用)
  • >0:表示进程共享(即不同进程间共享,需配合共享内存使用)
函数作用
sem_init初始化信号量
sem_destroy销毁信号量
sem_waitP操作,等待信号量,可能阻塞
sem_postV操作,释放资源
sem_getvalue获取当前信号量值

环形队列的生产消费模型

在这里插入图片描述
多线程如何在环形队列中进行生产和消费?(单生产,单消费,多生产多消费)

  • 队列为空,生产者先生产
  • 队列满了,消费者来消费
  • 队列不为空也不为满,让生产和消费同时进行

在这里插入图片描述
不让生产者把消费者套一个圈,也不能让消费者超过生产者

信号量就是用来进行互斥与同步

消费者关心的数据资源,生产者关心的是空间资源

在这里插入图片描述
消费者申请数据信号量取走了数据,缓冲区多出一个空位释放一个空位,通知生产者可以继续生产

同理生产者增加数据唤醒消费者

template <class T>
class RingQueue
{
public:RingQueue(int max_cap):_max_cap(max_cap),_ringqueue(max_cap){c_step = 0;p_step = 0;sem_init(&_data_sem, 0, 0);sem_init(&_space_sem, 0, max_cap);}void Equeue(const T &data){sem_wait(&_space_sem);_ringqueue[p_step] = data;p_step++;p_step %= _max_cap;sem_post(&_data_sem);}void Pop(T *data){sem_wait(&_data_sem);*data = _ringqueue[c_step];c_step++;c_step %= _max_cap;sem_post(&_space_sem);}~RingQueue(){sem_destroy(&_data_sem);sem_destroy(&_space_sem);}
private:std::vector<T> _ringqueue;int _max_cap;int c_step;int p_step;sem_t _data_sem;sem_t _space_sem;
};void *producer(void *args)
{RingQueue<int> *rq = static_cast<RingQueue<int> *>(args);int i = 0;while (1){rq->Equeue(i);cout << "生产者生产了" << i++ << endl;sleep(5);}
}
void*consumer(void *args)
{RingQueue<int> *rq = static_cast<RingQueue<int> *>(args);int i;while (1){rq->Pop(&i);cout << "消费者消费了" << i << endl;sleep(2);}
}int main()
{RingQueue<int> *rq=new RingQueue<int>(10);pthread_t c,p;pthread_create(&p, nullptr, producer,rq);pthread_create(&c, nullptr, consumer,rq);pthread_join(p, nullptr);pthread_join(c, nullptr);return 0;
}

如果现在是多生产多消费,我们就需要维护生产者和生产者,消费者和消费者之间的互斥关系

int main()
{RingQueue<Task> *rq=new RingQueue<Task>(10);pthread_t c,p;pthread_t c1, c2, p1, p2, p3;pthread_create(&c1, nullptr, consumer, rq);pthread_create(&c2, nullptr, consumer, rq);pthread_create(&p1, nullptr, producer, rq);pthread_create(&p2, nullptr, producer, rq);pthread_create(&p3, nullptr, producer, rq);pthread_join(c1, nullptr);pthread_join(c2, nullptr);pthread_join(p1, nullptr);pthread_join(p2, nullptr);pthread_join(p3, nullptr);return 0;
}

修改如下:

template <class T>
class RingQueue
{
public:RingQueue(int max_cap):_max_cap(max_cap),_ringqueue(max_cap){c_step = 0;p_step = 0;sem_init(&_data_sem, 0, 0);sem_init(&_space_sem, 0, max_cap);pthread_mutex_init(&_c_mutex, nullptr);pthread_mutex_init(&_p_mutex, nullptr);}void Equeue(const T &data){pthread_mutex_lock(&_p_mutex);sem_wait(&_space_sem);_ringqueue[p_step] = data;p_step++;p_step %= _max_cap;sem_post(&_data_sem);pthread_mutex_unlock(&_p_mutex);}void Pop(T *data){pthread_mutex_lock(&_c_mutex);sem_wait(&_data_sem);*data = _ringqueue[c_step];c_step++;c_step %= _max_cap;sem_post(&_space_sem);pthread_mutex_unlock(&_c_mutex);}~RingQueue(){sem_destroy(&_data_sem);sem_destroy(&_space_sem);pthread_mutex_destroy(&_c_mutex);pthread_mutex_destroy(&_p_mutex);}
private:std::vector<T> _ringqueue;int _max_cap;int c_step;int p_step;sem_t _data_sem;sem_t _space_sem;pthread_mutex_t _c_mutex;pthread_mutex_t _p_mutex;
};
void Equeue(const T &data)
{pthread_mutex_lock(&_p_mutex);sem_wait(&_space_sem);_ringqueue[p_step] = data;p_step++;p_step %= _max_cap;sem_post(&_data_sem);pthread_mutex_unlock(&_p_mutex);
}

这里到底应该先加锁还是先申请信号量?

如果先加锁的话,生产者先获得互斥锁,然后尝试申请一个空间信号量。如果空间信号量为0(表示队列已满),则生产者线程会在sem_wait处阻塞,同时它仍然持有互斥锁。这会导致其他生产者无法进入临界区

如果多个生产者同时尝试入队,其中一个生产者阻塞在信号量上,它会持有锁,阻止其他生产者检查队列或执行操作。这减少了系统的吞吐量

先申请信号量,再加锁,让每个生产者提前预定一个资源,再次加锁,轮到自己的时候生产即可,这样确保了锁只在被需要时才持有,并且持有时间最短

所以代码修改如下:

void Equeue(const T &data) {sem_wait(&_space_sem);  // 先等待空间信号量(不持有锁)pthread_mutex_lock(&_p_mutex); // 然后加锁保护共享数据_ringqueue[p_step] = data;p_step = (p_step + 1) % _max_cap;pthread_mutex_unlock(&_p_mutex);sem_post(&_data_sem);  // 发布数据信号量
}void Pop(T *data) {sem_wait(&_data_sem);  // 先等待数据信号量(不持有锁)pthread_mutex_lock(&_c_mutex); // 然后加锁保护共享数据*data = _ringqueue[c_step];c_step = (c_step + 1) % _max_cap;pthread_mutex_unlock(&_c_mutex);sem_post(&_space_sem);  // 发布空间信号量
}

思考一下,为什么这里不需要判断条件是否满足呢?

因为信号量本身就是判断条件!

sem_wait() 会原子地检查并减少计数器,如果需要则阻塞

sem_post() 会原子地增加计数器并唤醒等待者

信号量在外部可以不判断资源是否满足,就可以知道内部资源的情况!

条件变量在以下情况下可能更合适:

  • 需要基于复杂条件(不仅仅是计数)进行等待时

  • 需要广播通知所有等待线程时(虽然信号量也可以通过多次调用 sem_post 模拟)

  • 需要超时等待功能时(pthread_cond_timedwait)

但在简单的生产者-消费者模型中,信号量通常更直接和高效。

http://www.xdnf.cn/news/1437193.html

相关文章:

  • 09.01总结
  • LeetCode算法日记 - Day 30: K 个一组翻转链表、两数之和
  • 基于Springboot和Vue的前后端分离项目
  • playwright+python UI自动化测试中实现图片颜色和像素对比
  • milvus使用
  • Hard Disk Sentinel:全面监控硬盘和SSD的健康与性能
  • Python学习-day4
  • 2026届长亭科技秋招正式开始
  • 算法 --- 模拟
  • NLP学习系列 | Transformer代码简单实现
  • Zephyr如何注册设备实例
  • [Java]PTA:jmu-Java-01入门-取数字浮点数
  • 自学嵌入式第三十三天:网络编程-UDP
  • Day19(前端:JavaScript基础阶段)
  • 分布式中防止重复消费
  • Spring Security的@PreAuthorize注解为什么会知道用户角色?
  • 开悟篇Docker从零到实战一篇文章搞定
  • 基于Python毕业设计推荐:基于Django的全国降水分析可视化系统
  • 战略咨询——解读81页中小企业企业战略规划方案【附全文阅读】
  • go-mapus最简单的离线瓦片地图协作
  • C++后端开发重点知识点
  • Adafruit_nRF52_Bootloader 使用 uf2
  • Spring Cloud Config 核心原理
  • 【C++】编写通用模板代码的重要技巧:T()
  • CICD的持续集成与持续交付和Zabbix
  • 【C++】15. ⼆叉搜索树
  • 室内定位---apriltag 视觉定位demo
  • (四)Python控制结构(条件结构)
  • deepseek7b本地部署技巧,新手也能玩得转
  • 下载 | Win11 官方精简版,系统占用空间极少!(8月更新、Win 11 IoT物联网 LTSC版、适合老电脑安装使用)