深入了解linux系统—— POSIX信号量

POSIX信号量

POSIX信号量和SystemV信号量作用类似,都用于同步操作;

POSIX信号量可以用于线程间同步;

对于信号量,简单来说就一个计数器;

POSIX信号量相关接口:

信号量相关接口都在头文件<semaphore.h>

1. 初始化信号量

c 复制代码
int sem_init(sem_t *sem, int pshared, unsigned int value);

初始化信号量sem_init,存在三个参数:

sem:指针变量指向要初始化的信号量

pshared:传递0表示线程间共享,非0表示进程间共享

value:信号量的初始值

2. 销毁信号量

对于创建出来的信号量,在使用完后需要调用sem_destroy进行销毁。

c 复制代码
int sem_destroy(sem_t *sem);

3. P(申请)

等待信号量(申请),也就是P操作

c 复制代码
int sem_wait(sem_t *sem); //P()

调用sem_wait申请信号量,会将信号量的值减1

4. V(释放)

释放信号量,也就是V操作

c 复制代码
int sem_post(sem_t *sem);//V()

资源使用完毕,调用sem_post释放信号量,信号量的值加1

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

在之前基于blockqueue实现的生产者消费者模型中,使用队列当做生产者和消费者的交易场所(临界资源);

生产者和消费者在生产与消费时,都是将blockqueue当中一个整体,在一个线程访问时,其他的线程(生产者/消费者)都不能访问blockqueue

如果我们将临界资源分成几部分,生产者与消费者访问队列(临界资源)时,只访问其中的一部分;这样在生产者生产的过程中,消费者同时也就可以消费。

所以,我们就可以将队列分成几部分,每次访问时只访问其中的一部分。

但是,随着线程运行(生产者生产数据,消费者消费数据),如果使用普通的队列或者数组,其长度就会越来越长;所以,这里使用环形队列;

当环形队列为满时:生产者不能继续生产,阻塞等待。

当环形队列为空时:消费者不能继续消费,阻塞等待直到生产者生成数据。

当环形队列不为空,也不为满时:生产者可以生产数据,消费者可以消费数据(可以同时进行,访问不同的位置)。

1. 环形队列

要基于环形队列实现生产者消费者模型:

首先,要知道环形队列的大小;

生产者和消费者要访问不同的位置,那就也要将生产者和消费者所对应的位置(下标)记录下来;

其次,要维护生产者和生产者之间、消费者和消费者直接的互斥关系,就要存在所对应的互斥量(锁);

最后,要保证队列为满时,生产者不能继续生产;队列为空时,消费者不能继续消费;这里使用信号量(对信号量进行PV操作)

环形队列,可以使用数组或者vector来模拟

cpp 复制代码
    static int size_default = 5; // 队列默认大小
    template <class T>
    class ringqueue
    {
    public:
        ringqueue(int sz = size_default) : _sz(sz), _psub(0), _csub(0), _q(sz)
        {
            pthread_mutex_init(&_pmutex, nullptr);
            pthread_mutex_init(&_cmutex, nullptr);
            sem_init(&_empty, 0, _sz);
            sem_init(&_full, 0, 0);
        }
        ~ringqueue()
        {
            pthread_mutex_destroy(&_pmutex);
            pthread_mutex_destroy(&_cmutex);
            sem_destroy(&_full);
            sem_destroy(&_empty);
        }
    private:
        std::vector<T> _q;
        int _sz;                 // 环形队列大小
        int _psub;               // 生产下标
        int _csub;               // 消费下标
        pthread_mutex_t _pmutex; // 生产者互斥量
        pthread_mutex_t _cmutex; // 消费者互斥量
        sem_t _empty;            // 生产者信号量 --- 空位置
        sem_t _full;             // 消费者信号量 --- 满位置
    };

2. 生产

生产者要生产数据:

首先要判断是否存在空位置,就要申请一个信号量_emptyP(_empty);

其次,为了保证生产者和生产者的互斥关系,就要申请互斥量_pmutex

然后,生产者进行生成数据。

最后生产完数据,就可以释放互斥量_pmutex;此时队列中多了一个数据的,就要释放一个信号量_fullV(_full);

cpp 复制代码
        void Produce(const T &data)
        {
            sem_wait(&_empty);
            pthread_mutex_lock(&_pmutex);
            // 生产数据
            _q[_psub++] = data;
            _psub %= _sz;
            pthread_mutex_unlock(&_pmutex);
            sem_post(&_full);
        }

3. 消费

消费者要消费数据:

首先,判断队列中是否存在数据(满位置),就要申请一个信号量_fullP(_full);

其次,为了保证消费者和消费者之间的互斥关系,就要申请互斥量_cmutex

然后,消费者进行消费数据;

最后消费完数据,就可以释放互斥量_cmutex;此时队列中多了一个空位置,就要释放一个信号量_emptyV(_empty);

cpp 复制代码
        void Consume(T &data)
        {
            sem_wait(&_full);
            pthread_mutex_lock(&_cmutex);
            // 消费数据
            data = _q[_csub++];
            _csub %= _sz;
            pthread_mutex_unlock(&_cmutex);
            sem_post(&_empty);
        }

4. 测试

到此,就完成了环形队列的设计,现在来让生产者和消费者线程进行生产和消费:(测试)

cpp 复制代码
#include "ringqueue.hpp"
#include <unistd.h>
using namespace RingQueue;
template <class T>
class Thread
{
public:
    Thread(ringqueue<T> &rq, std::string name) : _name(name), _rq(rq) {}
    ~Thread() {}
    std::string _name;
    ringqueue<T> &_rq;
};
pthread_mutex_t pmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t cmutex = PTHREAD_MUTEX_INITIALIZER;
int count = 1;
void *produce(void *args)
{
    Thread<int> *pthread = static_cast<Thread<int> *>(args);
    while (true)
    {
        // 生产数据
        pthread_mutex_lock(&pmutex);
        pthread->_rq.Produce(count);
        std::cout << pthread->_name << " 生产了一个数据 : " << count << std::endl;
        count++;
        pthread_mutex_unlock(&pmutex);
        sleep(1);
    }
    return (void *)"1";
}
void *consume(void *args)
{
    Thread<int> *pthread = static_cast<Thread<int> *>(args);
    while (true)
    {
        // 消费数据
        sleep(1);
        pthread_mutex_lock(&cmutex);
        int msg = 0;
        pthread->_rq.Consume(msg);
        std::cout << pthread->_name << " 消费了一个数据 : " << msg << std::endl;
        pthread_mutex_unlock(&cmutex);
    }
    return (void *)"0";
}
int main()
{
    pthread_t t1, t2, t3, t4;
    ringqueue<int> rq;
    // t1,t2生产
    Thread td1(rq, "thread-1 ");
    pthread_create(&t1, nullptr, produce, &td1);
    Thread td2(rq, "thread-2 ");
    pthread_create(&t2, nullptr, produce, &td2);
    // t3,t4消费
    Thread td3(rq, "thread-3 ");
    pthread_create(&t3, nullptr, consume, &td3);
    Thread td4(rq, "thread-4 ");
    pthread_create(&t3, nullptr, consume, &td4);
    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);
    pthread_join(t4, nullptr);
    return 0;
}

在上述代码中,设计了Thread类方便测试;创建新线程thread-1和thread-2生产数据、新线程thread-3和thread-4消费数据。

相关推荐
刘一说3 小时前
CentOS部署ELK Stack完整指南
linux·elk·centos
从零开始的ops生活4 小时前
【Day 50 】Linux-nginx反向代理与负载均衡
linux·nginx
IT成长日记4 小时前
【Linux基础】Linux系统配置IP详解:从入门到精通
linux·运维·tcp/ip·ip地址配置
夜无霄4 小时前
安卓逆向(一)Ubuntu环境配置
linux·运维·爬虫·ubuntu
田野里的雨4 小时前
manticore离线安装(Ubuntu )
linux·运维·服务器·全文检索
wanhengidc4 小时前
云手机就是虚拟机吗?
运维·网络·安全·智能手机
Angletank4 小时前
虚拟机中centos简单配置
linux·经验分享·程序人生·centos
黑唐僧4 小时前
Linux 高阶命令-常用命令详解
linux
疾风铸境4 小时前
项目研发实录:电子称SDK封装dll给到QT和C#调用
linux·服务器·网络