news 2026/6/6 4:06:29

Linux —— 信号量

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux —— 信号量

目录

1. 信号量的引入

1.1 信号量的概念

1.2 本质的认识:

1.3 操作上的认识:

2. 信号量的接口

3. 321 CP场景

3.1 理解环形队列

3.2 单生产者,单消费者的场景:

3.2.1 理解

4. 代码实现

4.1 version1 -- 单生产,单消费

4.2 version2 -- 多生产,多消费


1. 信号量的引入

1.1 信号量的概念

  • 信号量的理论在之前的进程的文章中提过。
  • 在之前写的阻塞队列,生产消费模型的交易场所的那个队列是当作整体使用的,无法对一个队列进行任意地址访问,也是很难做,因为里面的数据结构不是很清楚到底是什么,但是可以将资源作为整体使用,之前举的例子:ATM机。
  • 将大块的资源拆分成一个一个的小块的资源,允许不同的线程访问同一个整体资源的不同部分,不就相当于允许多线程并发进入一个公共的资源。在使用共享资源,就可以分为两类:整体使用或者是不整体使用。
  • 整体使用就得有互斥能力,对应的就是互斥锁的技术。局部使用的场景就是:电影院。资源被局部使用的时候,最怕的就是两个问题:

一:一共10个资源,但是放入的线程数量是大于10的;

二:一共有10个资源,放入10个线程,但是让其中的两三个线程访问到了同一个资源

如果能够规避上面的两个问题,那么就可以让多线程并发访问,整体资源当中的一部分!!!

1.2 本质的认识:

1.3 操作上的认识:

信号量是一把计数器,跟一个整数一样,申请就--,释放就++。就可以将信号量理解成一个计数器,内部再加上一个锁,把信号量当成一个结构体,在结构体里有包含mutex互斥琐,再加一个对应的整数就可以了。

多线程访问,都得先申请信号量。但是前提是:都得先看到同一份信号量 - > 信号量本身就是 共享 / 临界资源 -> 信号量是为了保证公共资源的安全的 -> 所以PV操作要保证原子性!!!所以PV操作必须被做成原子的!!操作层面上的特性决定的。

2. 信号量的接口

sem_init():信号量的初始化

  • 头文件:<semaphore.h>
  • 信号量数据类型:sem_t
  • 推荐sem_init函数来进行初始化
  • pshared:表明进程间使用还是线程间使用,一般,0表示线程间使用,设置为0
  • value:信号量是一个计数器,表明初始值是多少
  • 返回值:sem_init() returns 0 on success; on error, -1 is returned, and errno is set to indicate the error。成功返回0,失败返回-1,错误码被设置
  • sem_t sem [n]:定义多个信号量

sem_destroy():信号量的销毁

  • sem:将 sem_init() 刚刚创建好的信号量传递进来
  • 返回值:sem_destroy() returns 0 on success; on error, -1 is returned, and errno is set to indicate the error.

sem_wait():减少信号量的值,对应的就是P操作

sem_post():释放一个信号量,计数器增加,对应的就是V操作

多进程的使用信号量:(了解)

直接用指针指向共享内存开头的位置,将该指针直接强转成信号类型,然后初始化,然后destroy,瞬间让两个进程可以用指针指向同一个共享内存的位置,把那一段共享内存初始化为信号量,被进程间使用。

3. 321 CP场景

生产者消费者模型基于环形队列

3.1 理解环形队列

但是之后的操作不用再来判断为空为满的情况,因为信号量本质就是一个计数器。

3.2 单生产者,单消费者的场景:

3.2.1 理解

其实,如果第一次为满的话,后面都得等消费一个再生产一个,消费一个生产一个,就变成了串行执行,效率是会下降的!!!但是消费一个生产一个这样的步调不是主流。

4. 代码实现

4.1 version1 -- 单生产,单消费

用代码完成以上逻辑

//Sem.hpp #pragma once #include <iostream> #include <semaphore.h> //两个信号量 class Sem { public: Sem(int num):_initnum(num) { sem_init(&_sem, 0, _initnum); } void P() { int n = sem_wait(&_sem); (void)n; } void V() { int n = sem_post(&_sem); (void)n; } ~Sem() { sem_destroy(&_sem); } private: sem_t _sem; int _initnum; };
//RingQueue.hpp #pragma once #include <iostream> #include <vector> #include "Sem.hpp" static int gcap = 5; template <typename T> class RingQueue { public: RingQueue(int cap = gcap) : _cap(cap), _ring_queue(cap), _space_sem(cap), _data_sem(0), _p_step(0), _c_step(0) { } void Pop(T *out) { // 先申请数据资源 _data_sem.P(); // 走到下面,要么是为满的情况,要么是不为空不为满的情况,不用担心生产者影响到自己 *out = _ring_queue[_c_step++]; _c_step %= _cap; _space_sem.V(); } void Enqueue(const T &in) { // 1. 先申请空间资源 _space_sem.P(); // 生产数据,有空间,在哪里呀? _ring_queue[_p_step++] = in; // 单生产单消费中,生产者生产的时候,消费者不能来进行打扰,因为,进入到生产中,一定不为满,所有就只有为空或者是不为空不为满的情况 // 不为空不为满二者可以并发执行,消费者并不影响生产者,为空的时候,生产者在运行期间,消费者的数据计数器为0,消费者不可能进入,就不会影响 // 所以单生产单消费中,申请到了信号量我在放数据的时候没有人能影响到我 // 维持环形特点 _p_step %= _cap; _data_sem.V(); } ~RingQueue() { } private: std::vector<T> _ring_queue; // 临界资源,环形队列的底层逻辑 int _cap; // 总容量大小,进行模运算 Sem _space_sem; // 空间资源,空间信号量 - 生产者关心 Sem _data_sem; // 数据资源,信号量是计数器,数据资源的数量 // 生产和消费的位置 int _p_step; int _c_step; };
//main.cc #include "RingQueue.hpp" #include <pthread.h> #include <unistd.h> void *consumer(void *args) { RingQueue<int> *rq = static_cast<RingQueue<int> *> (args); while(true) { sleep(1); int data = 0; rq->Pop(&data); //拿数据 std::cout << "消费了一个数据:" << data << std::endl; } } void *productor(void *args) { RingQueue<int> *rq = static_cast<RingQueue<int> *> (args); int data = 1; while(true) { rq->Enqueue(data); //入数据 std::cout << "生产了一个数据:" << data << std::endl; data++; } } int main() { RingQueue<int> *rq = new RingQueue<int>(); pthread_t c,p; pthread_create(&c, nullptr, consumer, (void*)rq); pthread_create(&p, nullptr, productor, (void*)rq); pthread_join(c, nullptr); pthread_join(p, nullptr); delete rq; return 0; }

运行结果:

问题1:上面的代码没有加锁呀!!!能保证数据的安全吗??

使用信号量,变相的完成了为空和为满的同步和互斥动作,不为空不为满指向的是不同的位置,访问的就是不同的位置。

问题2:怎么没有发现,在临界区内部,没有判断资源是否就绪啊??

是因为申请信号量是对资源的预定机制,对信号量P操作的时候,虽然是申请信号量,但是本质,就是对资源是否就绪进行判断!!!申请信号量是对资源的预定机制是在判断:从临界区内,转移到了临界区外部(或者入口处)

问题3:整体访问(用互斥锁),局部访问(用信号量)

代码中:

void Pop(T *out) { _data_sem.P(); *out = _ring_queue[_c_step++]; _c_step %= _cap; _space_sem.V(); } void Enqueue(const T &in) { _space_sem.P(); _p_step %= _cap; _data_sem.V(); }

最开始的时候,_data_sem.P(); 值为0,_space_sem.P();为10,申请数据信号量申请不到,不就相当于数据信号量减到0了嘛!申请资源申请不到,无法消费,就在这里阻塞;生产者只有生产完,V操作之后,数据资源才会有数据。当_data_sem.P(); 值为0的时候,不就相当于锁本身先被别人抢走了,锁变为了0,此时就被锁住,所以,互斥特性就在这里体现出来。

4.2 version2 -- 多生产,多消费

多生产者,多消费者,就必须得要新增维护生产者之间,消费者之间的互斥关系啦!

多个生产者同时生产,有可能会对同一个下标进行生产,下标只有一个,下标本身又成了临界资源,所以得加锁,所以需要几把锁??怎么加??

生产者和消费者之间的同步互斥关系已经由信号量自动维护了,生产者和生产者之间,消费者和消费者之间也是互斥的,所以得加锁,今天的消费者和生产者除了为空,除了为满,其它的情况都是生产和消费在并发的跑的,选用一把锁的话,就放弃了生产和消费的并发的情况。没错,但是会降低效率。所以选用两把锁,生产者之间共用一把锁,消费者之间共用一把锁。

生产者先内部竞争,选出一个生产者,同样的消费者之间也内部竞争,选出来一个消费者,选用两把锁,本质上还是单生产单消费。

#pragma once #include <iostream> #include <vector> #include "Sem.hpp" #include "Mutex.hpp" static int gcap = 5; template <typename T> class RingQueue { public: RingQueue(int cap = gcap) : _cap(cap), _ring_queue(cap), _space_sem(cap), _data_sem(0), _p_step(0), _c_step(0) { } void Pop(T *out) { // 先申请数据资源 _data_sem.P(); // 走到下面,要么是为满的情况,要么是不为空不为满的情况,不用担心生产者影响到自己 { LockGuard lockguard(&_c_lock); *out = _ring_queue[_c_step++]; _c_step %= _cap; } _space_sem.V(); } void Enqueue(const T &in) { // 1. 先申请空间资源,再加锁 _space_sem.P(); { LockGuard lockguard(&_p_lock); // 生产数据,有空间,在哪里呀? _ring_queue[_p_step++] = in; // 单生产单消费中,生产者生产的时候,消费者不能来进行打扰,因为,进入到生产中,一定不为满,所有就只有为空或者是不为空不为满的情况 // 不为空不为满二者可以并发执行,消费者并不影响生产者,为空的时候,生产者在运行期间,消费者的数据计数器为0,消费者不可能进入,就不会影响 // 所以单生产单消费中,申请到了信号量我在放数据的时候没有人能影响到我 // 维持环形特点 _p_step %= _cap; } _data_sem.V(); } ~RingQueue() { } private: std::vector<T> _ring_queue; // 临界资源,环形队列的底层逻辑 int _cap; // 总容量大小,进行模运算 Sem _space_sem; // 空间资源,空间信号量 - 生产者关心 Sem _data_sem; // 数据资源,信号量是计数器,数据资源的数量 // 生产和消费的位置 int _p_step; int _c_step; // 定义两把锁 Mutex _p_lock; Mutex _c_lock; };

运行结果:

打出来的结果有重复的,这是正常的,因为生产数据时多个线程在生产的时候,不是生产的时候数据拿重复了,也不是消费的时候数据拿重复了,是因为多个线程识别data的时候,enqueue的时候被同时访问了,data的本身的代码没有做保护,但是这个不影响。

资源整体使用,选用互斥锁,资源允许局部使用,选用信号量。

互斥锁和信号量本质就是一个变量,变量本质就是一段空间,所以想在多进程中使用也是可以使用的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/6 4:05:56

美团 x 云器|从美团BI平台升级看数据引擎架构升级演进路径

导读 本周&#xff0c;美团基础研发平台发布了《美团 BI 在指标平台和分析引擎上的探索和实践》一文&#xff0c;详细披露了其BI平台基于云器Lakehouse的引擎升级探索与实践。作为国内头部互联网公司的核心数据基础设施&#xff0c;美团的这一技术选型与实践经验&#xff0c;对…

作者头像 李华
网站建设 2026/6/6 4:02:10

增强现实眼镜公司US Orange Inc聘请顾问为纳斯达克IPO做准备

由硅谷资深发明家胡大文与名资公司本周共同成立的US Orange Inc&#xff08;“Orange”&#xff09;和Huaxia USA Corp.&#xff08;“华夏”&#xff09;&#xff0c;致力于商业化由太赫兹&#xff08;THz&#xff09;6G技术驱动的人工智能XR&#xff08;扩展现实&#xff09;…

作者头像 李华