目录

一:信号量相关函数

二:基于环形队列的生产消费模型

三:代码实现:


POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。但POSIX可以用于线程间同步。


一:信号量相关函数

初始化信号量:

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
 pshared:0表⽰线程间共享,⾮零表示进程间共享
 value:信号量初始值

销毁信号量:

int sem_destroy(sem_t *sem);

等待信号量:

功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()

发布信号量:

功能:发布信号量,表⽰资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()

上篇文章的生产者-消费者的例子是基于queue的,其空间可以动态分配,现在基于固定大小的环形队列重写这个程序(POSIX信号量):


二:基于环形队列的生产消费模型

环形队列采用数组模拟,用模运算来模拟环状特性:

其中环形队列默认是空和满的head和tail都是指向同一个位置的,解决办法:

1.使用一个计数器  2.对开一个位置,空指向开头,tail的next == head,表示满

 

但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程。

关于信号量的详细讲解:system V共享内存-CSDN博客

环形队列中每一个资源都用信号量表示,任何人访问临界资源之前,都要申请信号量

假设初始空间为:N,数据为:0(两个信号量)

对于生产者:看剩余空间

int tail = 0;

P(空间)

//给生产者空间,放入数据

ring[tail] = data;

tail++;

V(数据)

对于生产者:看剩余数据

int head = 0;

P(数据)

head++;

V(空间)

1.极端情况下生产者和消费者访问同一个位置

1.1为空:保证生产者原子性先生产

1.2为满:保证消费者原子性先消费

位置的空满体现了互斥与同步,具有访问先后顺序(通过信号量完成的互斥和同步)

2.不是极端情况下:

生产者和消费者一定不是同一个位置,可以同时进行并发访问。

即:同一个位置互斥同步,不是同一个位置并发

以上是基于单个生产者单个消费者,如果是多个生产者多个消费者呢?

生产和生产,消费和消费之间是互斥的,加2把锁,一次只能一个消费者和一个生产者访问环形队列,而生产和消费之间的关系由信号量维护


三:代码实现:

是先P资源还是先申请锁?

从效果实现来说,两个都可以保证实现同一个效果,但是先申请资源再申请锁更好

如果申请锁,再申请资源,如果资源不够,那一个线程就在阻塞等待,其他线程因为拿不到锁,在所的地方就等待,可能要进行两次等待,稍微慢一点

如果先申请资源,将所以满足条件的线程放进去,然后集体再争夺锁,这样争夺到锁,就可以直接使用资源,然后再释放锁,让其他线程使用了。

先申请资源再竞争锁,就好比限定人数的场子,先买限定数量票,然后让有票的人排队进入场子,只需要在验票处等待即可,票卖完之后,多余的人就不能排队验票进;如果是卖一个人验一张票,可能在卖票的地方就会拥堵

封装信号量:

#pragma once

#include <semaphore.h>

namespace SemModule
{
    int defaultsemval = 0;

    class Sem
    {
    public:
        Sem(int value = defaultsemval) : _init_val(value)
        {
            int n = ::sem_init(&_sem, 0, _init_val);
            (void)n;
        }

        void P()
        {
            int n = ::sem_wait(&_sem);
            (void)n;
        }

        void V()
        {
            int n = ::sem_post(&_sem);
            (void)n;
        }

        ~Sem()
        {
            int n = ::sem_destroy(&_sem);
            (void)n;
        }

    private:
        sem_t _sem;
        int _init_val;
    };

}

环形队列生产消费模型实现:

#pragma once

#include <iostream>
#include <vector>


#include "Sem.hpp"
#include "Mutex.hpp"

namespace RingBufferModule
{
    using namespace MutexModule;
    using namespace SemModule;

    template <typename T>
    class RingBuffer
    {
    public:

        RingBuffer(int cap)
        :_cap(cap)
        ,_p_step(0)
        ,_c_step(0)
        ,_ring(cap)
        ,_datasem(0)
        ,_spacesem(cap)
        {

        }

        //为什么不需要进行条件判断
        //信号量本身表示资源数目
        //只要P成功就表示一定有资源,不需要判断;如果不成功P就会失败,线程进入等待
        void Equeue(const T& in)
        {
            //生产者
            _spacesem.P();//申请空间,消耗空间

            {
                LockGuard lock(_p_lock);
                _ring[_p_step] = in;
                _p_step++;
                _p_step %= _cap;
            }

            _datasem.V();//生产数据,释放数据
        }

        void Pop(T *out)
        {
            //消费者
            _datasem.P();

            {
                LockGuard lock(_p_lock);
                *out = _ring[_c_step];
                _c_step++;
                _c_step %= _cap;
            }

            _spacesem.V();
        }

        ~RingBuffer()
        {

        }
    private:
        std::vector<T> _ring;//环,临界资源
        int _cap;           //总容量
        int _p_step;        //生产者位置
        int _c_step;        //消费者位置

        Sem _datasem;      //数据信号量
        Sem _spacesem;     //空间信号量

        Mutex _c_lock;
        Mutex _p_lock;
    };
}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐