初步了解Linux中的POSIX信号量及环形队列的CP模型

POSIX信号量

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

信号量的**本质就是一个计数器,而计数器的本质是用来描述我们的资源数目.**当我们申请信号量时,就是在判断我们是否有资源,加上锁的保护,就可以实现我们多线程的安全操作。、

信号量函数

sem_init (初始化信号量)
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
sem_t:头文件内封装的信号类型
pshared:0 表示线程间共享,非零表示进程间共享
value :信号量初始值
sem _destroy 摧毁信号量
#include <semaphore.h>

int sem_destroy(sem_t *sem);

参数:

sem:信号量变量

sem_wait 等待信号量
功能:等待信号量,会将信号量的值减 1,若信号量为零,则挂起等待
int sem_wait(sem_t *sem);

sem_post 发布信号量

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加 1,自动唤醒挂起的线程
int sem_post(sem_t *sem);
了解了信号量的作用以及他的使用函数之后,我们利用信号量来实现一个唤醒队列的生产消费模型的实例。

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

由于我们使用的是环形队列,所以我们希望在该消费模型中将我们的生产和消费的效率最大化,我们可以采用生产和消费同步进行。只要生产者和消费者不是在同一个位置进行生产和消费,我们就可以让他们同时进行。

我们来分析会出现的几种情况:

1、生产者和生产者之间:当我们环形队列没有被填满时,我们的生产者之间是互斥的关系,我在进行生产的时候,你就不可以来抢占我的位置(规定一次只能生产一位置的数据)。当填满时,就不可以在进行生产,需要进行等待。

2、消费者与消费者之间:消费者之间和生产者之间的关系是类似的互斥关系

3、生产者和消费者之间:当我们的环形队列为空时,消费者和生产者都在同一位置等待,此时他们二者是互斥的,并且只能让生产者执行,当环形队列不满时,我们的生产者和消费者之间是可以同时进行的,因为他们并没有在同一个位置上,当我们的队列满了时,生产者和消费者是互斥的,并且只能让消费者执行。

有了上述的分析,我们就可以很轻松的实现我们的CP模型啦。

重点还是在于我们的交易场所,代码如下:

cpp 复制代码
#pragma once

#include <vector>
#include <iostream>
#include <pthread.h>
#include <semaphore.h>

using namespace std;

#define defaultnum 5

template <class T>
class Ringqueue
{
private:
    void P(sem_t &sem)
    {
        sem_wait(&sem);
    }

    void V(sem_t &sem)
    {
        sem_post(&sem);
    }

    void Lock(pthread_mutex_t &mutex)
    {
        pthread_mutex_lock(&mutex);
    }

    void Unlock(pthread_mutex_t &mutex)
    {
        pthread_mutex_unlock(&mutex);
    }

public:
    Ringqueue(int cap = defaultnum) : cap_(cap),ringqueue_(cap),c_step_(0),p_step_(0)
    {
        sem_init(&cdata_sem_, 0, 0);
        sem_init(&pspace_sem_, 0, cap_);

        pthread_mutex_init(&c_mutex_, nullptr);
        pthread_mutex_init(&p_mutex_, nullptr);
    }

    void Push(const T&in)
    {
        P(pspace_sem_);

        Lock(p_mutex_);
        ringqueue_[p_step_] = in;
        p_step_++;
        p_step_%=cap_;
        Unlock(p_mutex_);

        V(cdata_sem_);
    }

    void Pop(T* out)
    {
        P(cdata_sem_);

        Lock(c_mutex_);
        *out = ringqueue_[c_step_];
        c_step_++;
        c_step_ %= cap_;
        Unlock(c_mutex_); 

        V(pspace_sem_);

    }

    ~Ringqueue()
    {
        sem_destroy(&cdata_sem_);
        sem_destroy(&pspace_sem_);

        pthread_mutex_destroy(&c_mutex_);
        pthread_mutex_destroy(&p_mutex_);
    }

private:
    vector<T> ringqueue_;
    int cap_;

    int c_step_;
    int p_step_;

    sem_t cdata_sem_;  // 消费者的信号量
    sem_t pspace_sem_; // 生产者的信号量

    pthread_mutex_t c_mutex_; // 消费的锁
    pthread_mutex_t p_mutex_; // 生产者的锁
};

变量设计:

  • vector<T>ringqueue_:用STL提供的vector来模拟实现环形队列。
  • **int cap_ :**用于控制我们的环形队列的长度
  • **int c_step_:**用于标记我们的消费者消费到了那个位置。
  • **int p_step_:**用于标记我们的生产者生产到了哪个位置。
  • sem_t cdata_sem_: 消费者的信号量,用于申请消费资源
  • sem_t pspace_sem_: 生产者的信号量,用于申请生产资源
  • **pthread_mutex_t c_mutex_:**消费者的锁,防止多消费者
  • pthread_mutex_t p_mutex_: 生产者的锁,防止多生产者

函数设计

**封装接口:**简单封装系统调用接口

//这些函数都是为了简单封装我们的系统调用接口

void P(sem_t &sem) //体现我们在信号量中的P操作

{

sem_wait(&sem);

}

void V(sem_t &sem)//体现我们在信号量中的V操作

{

sem_post(&sem);

}

void Lock(pthread_mutex_t &mutex)//封装加锁

{

pthread_mutex_lock(&mutex);

}

void Unlock(pthread_mutex_t &mutex)//封装解锁

{

pthread_mutex_unlock(&mutex);

}

构造函数

构造函数可接受用户自定传入的环形队列大小,同时将我们的生产则和和消费者的下标都置为0,同时初始化我们生产者和消费者的信号量和锁。

Ringqueue(int cap = defaultnum) : cap_(cap),ringqueue_(cap),c_step_(0),p_step_(0)

{

sem_init(&cdata_sem_, 0, 0);

sem_init(&pspace_sem_, 0, cap_);

pthread_mutex_init(&c_mutex_, nullptr);

pthread_mutex_init(&p_mutex_, nullptr);

}

Push函数(生产资源)

当我们生产资源的时候,首先要先申请生产者的信号量(P操作),如果有才能继续申请,否则挂起等待,申请成功后,对身生产者进行加锁,生产数据,然后将下标++并取模运算,解开生产者的锁,此时我们就以及生产了一个资源,就可以保证我们的消费者一定有数据消费,所以我们可以对消费者的信号量进行释放信号量(V操作),也就是告诉消费者有数据可以来读取了,能够唤醒挂起等待的消费者

void Push(const T&in)

{

P(pspace_sem_);

Lock(p_mutex_);

ringqueue_[p_step_] = in;

p_step_++;

p_step_%=cap_;

Unlock(p_mutex_);

V(cdata_sem_);

}

Pop函数(消费资源)

当我们消费资源的时候,需要申请消费者的信号量(P操作),如果有资源则申请成功,否则申请失败,挂起等待,申请成功后,对消费者进行加锁,取出资源消费数据,然后对消费者的下标进行++并进行模运算,解开消费者的锁,此时我们以及消费了一个资源,所以就一定有空间让生产者来进行生产,所以我们可以释放生产者的信号量(V操作),让生产者直到,有位置让生产者来进行生产了。

void Pop(T* out)

{

P(cdata_sem_);

Lock(c_mutex_);

*out = ringqueue_[c_step_];

c_step_++;

c_step_ %= cap_;

Unlock(c_mutex_);

V(pspace_sem_);

}

析构函数

摧毁掉所有的锁和信号量

~Ringqueue()

{

sem_destroy(&cdata_sem_);

sem_destroy(&pspace_sem_);

pthread_mutex_destroy(&c_mutex_);

pthread_mutex_destroy(&p_mutex_);

}

相关推荐
EnglishJun1 小时前
数据结构的学习(五)---树和二叉树
数据结构·学习·算法
Supernova_Jun2 小时前
Windows11 WSL2 镜像模式下 DNS 解析失效(Temporary failure resolving)
linux
AIR-IT2 小时前
国产ZYJ服务器RAID 5重建完整流程
服务器·国产·raid·浪潮
lyx49492 小时前
在 Win11 上用 Claude Code 接入 Gemini 模型(无需 WSL/Ubuntu)
linux·ubuntu·gemini模型·claude coode
m0_694845572 小时前
HandBrake 是什么?视频转码工具使用与服务器部署教程
服务器·前端·pdf·开源·github·音视频
郝亚军2 小时前
Ubuntu启一个http server,通过terminal测试通不通
linux·运维·ubuntu
ccino .2 小时前
【官方最新VMware workstation pro获取】
运维·网络安全·自动化
新新学长搞科研2 小时前
【CCF主办 | 高认可度会议】第六届人工智能、大数据与算法国际学术会议(CAIBDA 2026)
大数据·开发语言·网络·人工智能·算法·r语言·中国计算机学会
近津薪荼2 小时前
优选算法——前缀和(1):一维前缀和
c++·学习·算法