Linux44:POSIX信号量:

条件变量的封装:

Cond:lesson34

cpp 复制代码
#pragma once

#include <iostream>
#include <pthread.h>
#include "Mutex.hpp"

using namespace MutexModule;

namespace CondModule
{
    class Cond
    {
    public:
        Cond()
        {
            pthread_cond_init(&_cond, nullptr);
        }
        void Wait(Mutex &mutex)
        {
            int n = pthread_cond_wait(&_cond, mutex.Get());//从Mutex里面拿到锁
            (void)n;
        }
        void Signal()
        {
            // 唤醒在条件变量下等待的一个线程
            int n = pthread_cond_signal(&_cond);
            (void)n;
        }
        void Broadcast()
        {
            // 唤醒所有在条件变量下等待的线程
            int n = pthread_cond_broadcast(&_cond);
            (void)n;
        }
        ~Cond()
        {
            pthread_cond_destroy(&_cond);
        }
    private:
        pthread_cond_t _cond;
    };
};

1.回顾一下相关概念

POSIX信号量是一个标准,和System V是一样的。
信号量概念:

信号量本质是一个计数器,是对特定资源的预定机制。----->申请信号量成功,预定资源。

多线程使用资源的两种场景:

1.将目标资源整体使用【使用锁技术 / 2元信号量】,计数器为1的信号量:2元信号量

2.将目标资源按照不同的"块",分批使用【信号量】,如多生产多消费者模型

把目标资源划分成一块一块的小资源,就可以并发地放多个线程来并发地进行访问。 - 采用信号量技术

申请成果 - 资源地预定。

"注意不要访问同一个位置,不要放入过多线程进来。"
所有线程,都先看到sem(信号量),计数器,sem--,sem++

信号量本质也是临界资源

操作:PV操作

P: -- ,原子的,资源--

V:++,原子的,资源++

2.基于信号量设置生产者消费者模型&&环形队列的身缠这消费者模型

环形队列:

怎么判断环形队列是空的还是满的???

空和满的判断标准

(1)环形队列为空,head = tail

(2)为满,head = tail

解决方案:

1.定义一个计数器

2.空一格位置,head = tail --->空

判断tail的下一个位置是不是等于head--->证明队列已满。

怎么实现环形队列???

定义一个长度为N的数组,主要 %=,取模操作,满了就取模归0

举个例子

题意背景

大圆桌,上空格->盘子。

生产者放苹果,消费者拿苹果

约定:

约定1:空,生产者先运行

约定2:满,消费者先运行

约定3:生产者不能消费者套一个圈即以上

约定4:消费者不能超过生产者

思考:

1.只要我们不访问同一个位置,我们就可以同时进行

2.什么时候,我们会在同一个位置???

只有为空为满时,才会访问同一个位置。

为空:【互斥】,生产者【同步】先运行

为满:【互斥】,消费者【同步】先运行

结论:

结论1:环形队列,不为空,或者&&不为满,生产消费可以同时进行!

结论2:环形队列,为空 || 未满,生产消费需要同步互斥!

如何保证4个约定???----采用信号量保证!!

初始状态下:

生产者:

资源:环形队列中空地位置

初始sem_blank = N

消费者:

资源:环形队列中地数据

初始sem_data = 0

伪代码实现

伪代码操作,满足4个约定

信号量P原子,申请成功,继续运行

申请失败,申请的线程会被阻塞

3.熟悉接口

初始化信号量

cpp 复制代码
#include

int sem_init(sem_t *sem, int pshared, unsigned int value);

参数:

pshared:0表⽰线程间共享,⾮零表⽰进程间共享

value:信号量初始值

销毁信号量

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

等待信号量

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

发布信号量

cpp 复制代码
功能:发布信号量,表⽰资源使⽤完毕,可以归还资源了。将信号量值加1。

int sem_post(sem_t *sem);//V()

4.编写代码 ---多生产多消费

Sem.hpp

cpp 复制代码
#pragma once
#include<iostream>
#include<pthread.h>
#include <semaphore.h>
//信号量必须包含这个头文件

namespace SemModule{
    const int defaultvalue = 1;
    class Sem{
        public:
            //初始化
            Sem(unsigned int value = defaultvalue)//表示信号量的初始值
            {
                sem_init(&_sem,0,value);
            }
            //P操作
            void P()
            {
                sem_wait(&_sem);
            }
            //V操作
            void V()
            {
                sem_post(&_sem);
            }
            //灭掉
            ~Sem()
            {
                sem_destroy(&_sem);
            }
        private:
            sem_t _sem;
    };
}

RingQueue.hpp环形队列

cpp 复制代码
#pragma once
#include<iostream>
#include<pthread.h>
#include<vector>
#include"Mutex.hpp"
#include"Sem.hpp"

using namespace std;
using namespace MutexModule;
using namespace SemModule;

template<typename T>
class RingQueue{
    public:
        RingQueue(int cap = 5)
        :_cap(cap)
        ,_rq(cap)//设置环形队列的空间大小
        ,_blank_sem(cap)//生产者的信号量大小
        ,_p_loc(0)
        ,_data_sem(0)
        ,_c_loc(0)//初始化从下到上
        {}

        void Equeue(const T& in)
        {
            //先分配资源,然后在竞争锁,这样的效率更高
            _blank_sem.P();
            {
                LockGuard lock(_pmutex);
                _rq[_p_loc] = in;
                _p_loc++;
                _p_loc%=_cap;
            }
            _data_sem.V();
        }

        void Pop(T*out)
        {
            _data_sem.P();
            {
                LockGuard lockguard(_cmutex);
                *out = _rq[_c_loc];
                _c_loc++;
                _c_loc%=_cap;
            }
            _blank_sem.V();
        }

        ~RingQueue(){}

    private:
        vector<T> _rq;//环形队列
        int _cap;//空间大小

        //生产者
        Sem _blank_sem;//空的数量
        int _p_loc;//生产者的下标

        //消费者
        Sem _data_sem;//有数据的数量
        int _c_loc;

        Mutex _pmutex;//生产锁
        Mutex _cmutex;//消费锁
};

Mutex.hpp

cpp 复制代码
#pragma once
#include<iostream>
#include<pthread.h>

namespace MutexModule{
    class Mutex{
        public:
            Mutex(){
                pthread_mutex_init(&_mutex,nullptr);
            }
            void Lock()
            {
                int n = pthread_mutex_lock(&_mutex);
            }

            void Unlock()
            {
                int n = pthread_mutex_unlock(&_mutex);
            }

            pthread_mutex_t* Get()
            {
                return &_mutex;
            }

            ~Mutex()
            {
                pthread_mutex_destroy(&_mutex);
            }
        private:
            pthread_mutex_t _mutex;
    };
    class LockGuard{
        public:
            LockGuard(Mutex& mutex)
                :_mutex(mutex)
                {
                    _mutex.Lock();
                }
            ~LockGuard()
            {
                _mutex.Unlock();
            }
        private:
            Mutex _mutex;
    };
}

Main.cc

cpp 复制代码
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include"RingQueue.hpp"
#include<cstring>
struct threaddata
{
    RingQueue<int>* rq;
    string name;
};

void* product(void*args)
{
    int data = 0;
    threaddata* ss = static_cast<threaddata*>(args);
    while(true)
    {
        sleep(2);
        ss->rq->Equeue(data);
        cout<<ss->name<<"生产一个:"<<data<<endl;
        data++;
    }
}
void* consume(void*args)
{
    threaddata* ss = static_cast<threaddata*>(args);
    while(true)
    {
        sleep(3);
        int t = 0;
        ss->rq->Pop(&t);
        cout<<ss->name<<"消费了一个"<<t<<endl;
    }
}

int main()
{
    RingQueue<int>* rq = new RingQueue<int>();
    pthread_t p[3],c[2];

    threaddata* p1 = new threaddata();
    p1->rq = rq;
    p1->name = "p-thread-1";
    pthread_create(p,nullptr,product,p1);
    
    threaddata* p2 = new threaddata();
    p2->rq = rq;
    p2->name = "p-thread-2";
    pthread_create(p+1,nullptr,product,p2);


    threaddata* p3 = new threaddata();
    p3->rq = rq;
    p3->name = "p-thread-3";
    pthread_create(p+2,nullptr,product,p3);


    threaddata* c1 = new threaddata();
    c1->rq = rq;
    c1->name = "c-thread-1";
    pthread_create(c,nullptr,consume,c1);


    threaddata* c2 = new threaddata();
    c2->rq = rq;
    c2->name = "c-thread-2";
    pthread_create(c+1,nullptr,consume,c2);


    pthread_join(p[0], nullptr);
    pthread_join(p[1], nullptr);
    pthread_join(p[2], nullptr);

    pthread_join(c[0], nullptr);
    pthread_join(c[1], nullptr);
    return 0;
}

Makefile

cpp 复制代码
ring:Main.cc
	g++ -o $@ $^ -lpthread
.PHONY:clean
clean:
	rm -f ring

问题:单生产单消费是通过给整体上锁来实现的,那么多生产多消费呢????

cpp 复制代码
 void Equeue(const T& in)
        {
            //先分配资源,然后在竞争锁,这样的效率更高
            _blank_sem.P();
            {
                LockGuard lock(_pmutex);
                _rq[_p_loc] = in;
                _p_loc++;
                _p_loc%=_cap;
            }
            _data_sem.V();
        }

        void Pop(T*out)
        {
            _data_sem.P();
            {
                LockGuard lockguard(_cmutex);
                *out = _rq[_c_loc];
                _c_loc++;
                _c_loc%=_cap;
            }
            _blank_sem.V();
        }

是通过定义两个锁,来实现的,满足4个约定。有信号量,上。无信号量,等。

先分配锁还是先分配资源???

先划分资源,在申请锁--->效率高。先买票,再排队

先加锁,才可以申请资源,--->效率低,先排队再买票

结论:先分配资源,然后再竞争锁,不然会导致效率低。

相关推荐
WZTTMoon2 小时前
Spring Boot 启动报错:OpenFeign 隐性循环依赖,排查了整整一下午
java·spring boot·后端·spring cloud·feign
俩个逗号。。2 小时前
Kotlin 扩展函数详解
开发语言·kotlin
苏渡苇2 小时前
Stream.collect() 的花式玩法:Collector.of() 自定义收集器
java·stream·jdk21·collector·jdk8+·自定义收集器
丶小鱼丶2 小时前
数据结构和算法之【队列】
java·数据结构
Ronin3053 小时前
【Qt常用控件】容器类控件和布局管理器
开发语言·qt·常用控件·布局管理器·容器类控件
菜鸡儿齐5 小时前
Unsafe方法学习
java·python·学习
汤姆yu5 小时前
IDEA接入Claude Code保姆级教程(Windows专属+衔接前置安装)
java·windows·intellij-idea·openclaw·openclasw安装
prince058 小时前
用户积分系统怎么设计
java·大数据·数据库
967710 小时前
理解IOC控制反转和spring容器,@Autowired的参数的作用
java·sql·spring