Linux学习之信号量

目录

信号量 posix

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

池化技术

线程池

单例模式


信号量 posix

POSIX 信号量和 SystemV 信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但 POSIX 可以用于线程间同步。
对于一个较大的资源数是可以被分成多份的,这时候我们也会使用信号量表示资源数目。
以电影院举例子:我们的共享资源就是电影院的座位,我们就相当于进程,我们想要获取共享资源,不是立马就去获取共享资源,而是要先买票预定,而电影票就相当于信号量,只有当信号量还存在时,大于零,我们才可以去获取资源。
所以信号量的本质我们可以理解为就是资源的计数器,申请信号量,也就简洁的判断了线程能否进行资源访问了。
线程的信号量相较于systemV的更加简单,我们先来认识一些接口:
初始化信号量
#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); 
发布信号量
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加 1
int sem_post(sem_t *sem);
上一节生产者 - 消费者的例子是基于 queue , 其空间可以动态分配 , 现在基于固定大小的环形队列重写这个程序 POSIX信号量)。

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

环形队列的生产消费模型,利用数组的下标通过取模运算,模拟环形队列,使用两个下标记录分别表示消费者,与生产者的消费与生产,类似双指针。当两个下标相遇即为 队列为满 或者空,我们不知道,因此我们还引用了信号量分别记录,生产者需要的空间大小,以及消费者需要的资源大小。

通过两个下标分别表示生产与消费,从而实现了避免互斥的情况,即不需要加锁。

RingQueue.hpp

//设计环形队列
static int defaltcapacity=10;
template<class T>
class RingQueue
{
    private:
    void P(sem_t &sem)
    {
        sem_wait(&sem);
    }
    void V(sem_t &sem)
    {
        sem_post(&sem);
    }
    
    public:
    RingQueue(int capacity=defaltcapacity):_rq(capacity),_capacity(capacity),c_index(0),p_index(0)
    {
        sem_init(&p_sem_cap,0,_capacity);
        sem_init(&c_sem_data,0,0);
    }
    int GetIndex()
    {
        return p_index;
    }
    void Push(const T& temp)
    {
        //对信号量进行PV操作
        P(p_sem_cap);   //空间减一

        _rq[p_index]=temp;
        V(c_sem_data);  //资源加一

       //之后位置后移,保持环状
       p_index++;
       p_index%=_capacity;
        
    }
    void Pop(T *out)
    {
        P(c_sem_data);
        //用消费者下标表示消费
        *out=_rq[c_index];
        V(p_sem_cap);

        c_index++;
        c_index%=_capacity;

    }

    ~RingQueue()
    {
        sem_destroy(&p_sem_cap);
        sem_destroy(&c_sem_data);
    }

    private:
    std::vector<T> _rq;       //环形队列
    //生产与消费 可以当作两个指针,这里就是下标,相遇可能是满,可能是空,这时取决于信号量
    int _capacity;            //容量

    int c_index;              //生产者下标
    int p_index;              //消费者下标
    //使用了两个下标,从而不会产生互斥

    sem_t p_sem_cap;          //生产者空间信号量
    sem_t c_sem_data;         //消费者资源信号量 

};

 那如果是多生产多消费模型,生产者与生产者之间相互竞争下标,消费者与消费者之间相互竞争下标,因此生产的时候与消费的时候,需要加锁。

需要注意一点,信号量的申请与所得申请的顺序:

先申请信号量,再申请锁是较好的,先申请信号量同时也会申请锁,一申请锁成功,我们进入执行了,其次信号量数据不需要让锁保护着,他们两不相关。

增加以及修改的部分:


    void Lock(pthread_mutex_t &mutex)
    {
        pthread_mutex_lock(&mutex);
    }
    void Unlock(pthread_mutex_t &mutex)
    {
        pthread_mutex_unlock(&mutex);
    }
    public:
    RingQueue(int capacity=defaltcapacity):_rq(capacity),_capacity(capacity),c_index(0),p_index(0)
    {
        sem_init(&p_sem_cap,0,_capacity);
        sem_init(&c_sem_data,0,0);
        pthread_mutex_init(&pmutex,nullptr);
        pthread_mutex_init(&cmutex,nullptr);
    }
    void Push(const T& temp)
    {
        {
         //上锁
             P(p_sem_cap);   //空间减一
         Lock(pmutex);
             //对信号量进行PV操作
             _rq[p_index]=temp;
             //之后位置后移,保持环状
             p_index++;
             p_index%=_capacity;
         Unlock(pmutex);
             V(c_sem_data);  //资源加一
        }  
    }
    void Pop(T *out)
    {
        {
        //上锁
            P(c_sem_data);
        Lock(cmutex);
            //用消费者下标表示消费
            *out=_rq[c_index];
            c_index++;
            c_index%=_capacity;
        Unlock(cmutex); 
            V(p_sem_cap);
        }
    }

    ~RingQueue()
    {
        sem_destroy(&p_sem_cap);
        sem_destroy(&c_sem_data);
        pthread_mutex_destroy(&pmutex);
        pthread_mutex_destroy(&cmutex);
    }

   

    //多线程情况下我们用两把锁分别让生产者与消费者按顺序来
    pthread_mutex_t pmutex;
    pthread_mutex_t cmutex;

};

池化技术

本质:以空间换时间

把一些资源预先分配好,组织到对象池中,之后的业务使用资源从对象池中获取,使用完后放回到对象池中。

线程池

这里以一个简单的c++类内的原生线程为例:

struct ThreadInfo
{
    ThreadInfo()
    {}
    ThreadInfo(pthread_t id,std::string Name):tid(id),name(Name)
    {}
    pthread_t tid;
    std::string name;
};
static const int defaultnum =5;
template<class T>class ThreadPool
{   

    private:
    void Lock()
    {
        pthread_mutex_lock(&_mutex);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&_mutex);
    }
    void Wake()
    {
        pthread_cond_signal(&_cond);
    }
    void Wait()
    {
        pthread_cond_wait(&_cond,&_mutex);
    }
    std::string GetThreadName(pthread_t tid)
    {
        for (const auto &ti : threads)
        {
            if (ti.tid == tid)
                return ti.name;
        }
        return "None";
    }
    public:
        ThreadPool(int capacity=defaultnum):threads(capacity),_capacity(capacity)
        {
            pthread_cond_init(&_cond,nullptr);
            pthread_mutex_init(&_mutex,nullptr);
        }
        static void *Handler(void *args)
        {
            ThreadPool<T> *p = static_cast<ThreadPool<T> *>(args);
            std::string name = p->GetThreadName(pthread_self());
            while(true)
            {
                p->Lock();
                //没有任务,阻塞队列等待
                while(p->_task.empty())
                {
                   p->Wait();
                }
                T t = p->Pop();
                p->Unlock();
                t();
                std::cout << name << " run, "<< "result: " << t.GetResult() << std::endl;
            }
            

        }
        void start()
        {

            for(int i=0;i<threads.size();i++)
            {
                threads[i].name="thread_"+std::to_string(1+i);
                pthread_create(&(threads[i].tid),nullptr,Handler,this);
            }
        }

        void Push(const T& temp)
        {
            //生产任务
            Lock();
            _task.push(temp);
            //有任务了。可以唤醒线程
            Wake();
            Unlock();
        }
        T Pop()
        {
            T t=_task.front();
            _task.pop();
            return t;
        }

        ~ThreadPool()
        {
            pthread_mutex_destroy(&_mutex);
            pthread_cond_destroy(&_cond);
        }

    private:
        std::vector<ThreadInfo> threads; //线程队列
        std::queue<T> _task;//任务队列
        pthread_mutex_t _mutex;
        pthread_cond_t _cond;
        int _capacity;

};

这里在编译的时候,会有个关于线程函数的报错信息,因为线程函数的参数为void *,函数返回值为void *,但当我们在类内写的时候,此时参数默认会有一个类的this指针,函数类型就匹配不上了,这里可以放在类外,或者修饰为静态成员函数。

主程序:

int main()
{
    //刚开始,我们的线程池时刻就准备着了,有任务就开始处理
    ThreadPool<Task> *p=new ThreadPool<Task>(20);
    p->start();
    srand(time(NULL)^getpid());
    while(true)
    {
        //创建任务
        int x = rand() % 10 + 1;
        usleep(10);
        int y = rand() % 5;
        char op = opers[rand()%opers.size()];
        Task t(x, y, op);
        cout<<"hanldle task:"<<t.GetTask()<<endl;
       //把任务将给线程池处理
        p->Push(t);
        sleep(1);
       
    }
    return 0;
}

单例模式

早在c++的时候我们就已经学过了单例模式,所谓的单例模式是一种设计模式,只允许有一个对象,单例模式分为饿汉与懒汉两种模式,区别就在于饿汉是直接创建这个对象,而懒汉是在用的时候才创建,两者无本质区别,主要是延迟创建了 。

我们今天简单的看一下饿汉与懒汉的代码:

饿汉方式实现单例模式
template <typename T>
class Singleton {
static T data;
public:
static T* GetInstance() {
return &data;
}
};
懒汉方式实现单例模式
template <typename T>
class Singleton {
static T* inst;
public:
static T* GetInstance() {
if (inst == NULL) {
inst = new T();
}
return inst;
}
};

而在线程中,考虑到线程安全的问题,我们一般都会把线程池封装成一个类,且只有一个对象。

懒汉方式实现单例模式(线程安全版本)

// 懒汉模式, 线程安全
template <typename T>
class Singleton {
volatile static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.
static std::mutex lock;
public:
static T* GetInstance() {
if (inst == NULL) { // 双重判定空指针, 降低锁冲突的概率, 提高性能.
lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 new.
if (inst == NULL) {
inst = new T();
}
lock.unlock();
}
return inst;
}
};

现在,我们就来将我们的线程池封装一下:

主要是禁用拷贝,赋值,获取对象指针,类外初始化,类内定义,注意设置为静态,私有。

struct ThreadInfo
{
    ThreadInfo()
    {}
    ThreadInfo(pthread_t id,std::string Name):tid(id),name(Name)
    {}
    pthread_t tid;
    std::string name;
};
static const int defaultnum =5;
template<class T>class ThreadPool
{   

    private:
    void Lock()
    {
        pthread_mutex_lock(&_mutex);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&_mutex);
    }
    void Wake()
    {
        pthread_cond_signal(&_cond);
    }
    void Wait()
    {
        pthread_cond_wait(&_cond,&_mutex);
    }
    std::string GetThreadName(pthread_t tid)
    {
        for (const auto &ti : threads)
        {
            if (ti.tid == tid)
                return ti.name;
        }
        return "None";
    }
    public:
       
        static void *Handler(void *args)
        {
            ThreadPool<T> *p = static_cast<ThreadPool<T> *>(args);
            std::string name = p->GetThreadName(pthread_self());
            while(true)
            {
                p->Lock();
                //多个线程来处理任务,没有任务,阻塞队列等待
                while(p->_task.empty())
                {
                   p->Wait();
                }
                T t = p->Pop();
                p->Unlock();
                t();
                std::cout << name << " run, "<< "result: " << t.GetResult() << std::endl;
            }
            

        }
        void start()
        {

            for(int i=0;i<threads.size();i++)
            {
                threads[i].name="thread_"+std::to_string(1+i);
                pthread_create(&(threads[i].tid),nullptr,Handler,this);
            }
        }

        void Push(const T& temp)
        {
            //生产任务
            Lock();
            _task.push(temp);
            //有任务了。可以唤醒线程
            Wake();
            Unlock();
        }
        T Pop()
        {
            T t=_task.front();
            _task.pop();
            return t;
        }

        ~ThreadPool()
        {
            pthread_mutex_destroy(&_mutex);
            pthread_cond_destroy(&_cond);
        }

       static ThreadPool<T>* GetInstance()
        {
            if(_tp==nullptr)
            {
                _tp=new ThreadPool<T>();
            }
            return _tp;
        }
    private:
        std::vector<ThreadInfo> threads; //线程队列
        std::queue<T> _task;//任务队列
        pthread_mutex_t _mutex;
        pthread_cond_t _cond;
        int _capacity;

        static ThreadPool<T>* _tp;

    private:
     ThreadPool(int capacity=defaultnum):threads(capacity),_capacity(capacity)
        {
            pthread_cond_init(&_cond,nullptr);
            pthread_mutex_init(&_mutex,nullptr);
        }
    ThreadPool(const ThreadPool<T> & temp)=delete;

    const ThreadPool<T>& operator=(ThreadPool<T>& p)=delete;

};
//初始化对象
template<class T>
ThreadPool<T>* ThreadPool<T>:: _tp=nullptr

那如果在获取对象指针的时候是多线程并发访问,那么如何处理?

因此我们还需要加锁,创建全局的pthread_mutex_t:

 //类内
static ThreadPool<T>* GetInstance()
        {
            if(_tp==nullptr)//在判断一次,只要被创建了,就没必要再有线程来申请了
           {
            pthread_mutex_lock(&_mutex_);
            if(_tp==nullptr)
            {
                _tp=new ThreadPool<T>();
            }
            pthread_mutex_unlock(&_mutex_)
           }
            return _tp;
            
        }
   static pthread_mutex_t _mutex_;
//类外

template<class T>
pthread_mutex_t ThreadPool<T>::_mutex=PTHREAD_MUTEX_INITIALIZAR;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜菜求佬带

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值