【Linux进程篇】进程终章:POSIX信号量&&线程池&&线程安全的单例模式&&自旋锁&&读者写者问题

W...Y的主页  😊

代码仓库分享 💕 

前言:在之前的进程间通信时我们就讲到过信号量,他的本质就是一个计数器,用来描述临界资源的一个计数器。我们当时使用电影院的例子来说明信号量。电影院的座位被我们称为临界资源,只有买到票才能有座位去看电影,申请信号量就是预定买票,申请成功才可以继续往下走下去。而我们看完电影就会释放临界资源,这些资源就可以被别人申请了。

目录

POSIX信号量 

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

线程池

STL,智能指针和线程安全

线程安全的单例模式

 什么是单例模式

什么是设计模式 

单例模式的特点 

饿汉实现方式和懒汉实现方式 

饿汉方式实现单例模式 

懒汉方式实现单例模式 

其他常见的各种锁

自旋锁

读者写者问题

读写锁接口


POSIX信号量 

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() 

上述就是我信号量的基本操作,现在我们需要实现一个基于环形队列一个生产消费者模型,我们为什么不用上篇博客中的阻塞队列呢。因为阻塞队列我们将其看作一个整体只能进行首尾操作,不能访问队列中间内容。而环形队列我们使用的是vecotor数组进行模拟,可以使用下标进行访问中间内容。

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

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

当hend与tail指向同一块位置时,数组可能为满也可能为空,其余的时候可以进行并发访问数组。 

环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态。

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

ringqueue.hpp 

#pragma once
#include<iostream>
#include<vector>
#include<semaphore.h>
#include<pthread.h>
using namespace std;
template<typename T>
class RingQueueu
{
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:
    RingQueueu(int cap)
        :_cap(cap),_ring_queue(cap)
    {
        sem_init(&_room_sem,0,_cap);
        sem_init(&_data_sem,0,0);
        pthread_mutex_init(&_productor_mutex,nullptr);
        pthread_mutex_init(&_consumer_mutex,nullptr);
    }
    void Enqueue(const T& in)
    {
        Lock(_productor_mutex);
        P(_room_sem);
        _ring_queue[_productor_step++] = in;
        _productor_step %= _cap;
        V(_data_sem);
        Unlock(_productor_mutex);
    }
    void Pop(T* out)
    {
        Lock(_consumer_mutex);
        P(_data_sem);
        *out = _ring_queue[_consumer_step++];
        _consumer_step %= _cap;
        V(_room_sem);
        Unlock(_consumer_mutex);
    }
    ~RingQueueu()
    {
        sem_destroy(&_room_sem);
        sem_destroy(&_data_sem);
        pthread_mutex_destroy(&_productor_mutex);
        pthread_mutex_destroy(&_consumer_mutex);
    }
private:
    vector<T> _ring_queue;
    int _cap;
    int _productor_step = 0;
    int _consumer_step = 0;
    sem_t _room_sem;
    sem_t _data_sem;
    //加锁,维护多生产多消费
    pthread_mutex_t _productor_mutex;
    pthread_mutex_t _consumer_mutex;
};

单生产单消费时可以不用加锁,而多生产多消费时就需要加锁防止同时访问临界资源。

而加锁解锁应该放在申请信号量的后面进行才是比较好的,为什么呢?因为申请信号量是预定机制是原子的,不会出现线程安全问题, 这样可以先预定再等锁,锁来了之后直接运行后面代码,可以提高效率。(等也是等,不如再等的时候申请信号量)

所以这样写更好:

void Enqueue(const T &in)
    {
        P(_room_sem);
        Lock(_productor_mutex);
        _ring_queue[_productor_step++] = in;
        _productor_step %= _cap;
        Unlock(_productor_mutex);
        V(_data_sem);
    }
    void Pop(T *out)
    {
        P(_data_sem);
        Lock(_consumer_mutex);
        *out = _ring_queue[_consumer_step++];
        _consumer_step %= _cap;
        Unlock(_consumer_mutex);
        V(_room_sem);
    }

main.cc

#include "RingQueue.hpp"
#include "Thread.hpp"
#include "Task.hpp"
#include <string>
#include <vector>
#include <unistd.h>
#include <ctime>
int a = 10;
using namespace ThreadModule;
using namespace std;
using ringqueue_t = RingQueueu<Task>;

void PrintHello()
{
    cout << "hello" << endl;
}
void Consumer(ringqueue_t &rq)
{
    while (true)
    {
        Task t;
        rq.Pop(&t);
        t();
    }
}
void Productor(ringqueue_t &rq)
{
    
    while (true)
    {
        sleep(1);
        rq.Enqueue(Download);
        
    }
}
void InitComm(std::vector<Thread<ringqueue_t>> *threads, int num, ringqueue_t &rq, func_t<ringqueue_t> func)
{
    for (int i = 0; i < num; i++)
    {
        std::string name = "thread-" + std::to_string(i + 1);
        threads->emplace_back(func, rq, name);
        //(*threads)[threads->size()-1].Start();
        //threads->back().Start();
    }
}
void InitConsumer(std::vector<Thread<ringqueue_t>> *threads, int num, ringqueue_t &rq)
{
    InitComm(threads, num, rq, Consumer);
}

void InitProductor(std::vector<Thread<ringqueue_t>> *threads, int num, ringqueue_t &rq)
{
    InitComm(threads, num, rq, Productor);
}
void WaitAllThread(std::vector<Thread<ringqueue_t>> threads)
{
    for(auto thread : threads)
    {
        thread.Join();
    }
}
void StartAll(vector<Thread<ringqueue_t>>& threads)
{
    for(auto& thread : threads)
    {
        thread.Start();
    }
}
int main()
{
    ringqueue_t *bq = new ringqueue_t(10);
    std::vector<Thread<ringqueue_t>> threads;
    InitConsumer(&threads, 1, *bq);
    InitProductor(&threads, 1, *bq);
    StartAll(threads);
    WaitAllThread(threads);
    return 0;
}

这里使用的也不是原生线程库,而是我们自己封装的thread。上述代码都是完整无误的,但是这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

W…Y

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

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

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

打赏作者

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

抵扣说明:

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

余额充值