Linux--线程安全的单例模式--自旋锁--0211

1. 线程安全的单例模式

1.1 什么是单例模式

某些类, 只应该具有一个对象(实例), 就称之为单例.

1.1.1 懒汉方式实现单例模式

以上篇博文的线程池为例

Liunx--线程池的实现--0208 09_Gosolo!的博客-CSDN博客

  • 实现懒汉模式首先要先将构造函数私有化,并禁止生成默认的拷贝构造函数,赋值运算符重载,不能随意的构造出来对象。
  • 其次增加一个静态的线程池对象,才符合单例的条件。
  • 然后需要有一个函数可以用来构造对象,由于该函数需要知道静态线程池对象是否不为空,也就是需要访问静态成员,所以该函数也必须是静态的
template<class T>
class ThreadPool
{
    //...
private:
    ThreadPool(int thread_num=g_thread_num)
        :num_(thread_num)
    {
        //创造线程的空间 构造线程
        for(int i=1;i<=num_;i++)
        {
            //每个线程的编号 回调函数 输出型参数
            threads_.push_back(new Thread(i,routine,this)); 
        }
        pthread_mutex_init(&lock,nullptr);
        pthread_cond_init(&cond,nullptr);
    }
    ThreadPool(const ThreadPool<T>& other)=delete;
    const ThreadPool<T> operator=(const ThreadPool<T>& other)=delete;
    //...
public:
    static ThreadPool<T>* getThreadPool(int num=g_thread_num)
    {
        if(nullptr==thread_ptr)
        {
            thread_ptr=new ThreadPool<T>(num);
        }
        return thread_ptr;
    }
private:
    std::vector<Thread*> threads_;
    int num_;
    std::queue<T> task_queue_;
    pthread_mutex_t lock;//保护临界区(任务队列)的一把锁
    pthread_cond_t cond;

    static ThreadPool<T>* thread_ptr;//静态成员 在类外初始化
};
template<class T>
ThreadPool<T>* ThreadPool<T>::thread_ptr =nullptr;

 问题:

由于我们这个项目是线程池,有没有可能多个线程同时访问getThreadPool函数,都进入到了thread_ptr与nullptr这一步判断?

会的,这里是线程不安全的,我们需要加锁以保证安全。

1.1.2 懒汉方式实现线程安全的单例模式

在静态成员函数中加锁,所以该锁也需要是静态的成员变量。

template<class T>
class ThreadPool
{
    //...
private:
    ThreadPool()
    {}
public:
    static ThreadPool<T>* getThreadPool(int num=g_thread_num)
    {
        if(nullptr==thread_ptr)
        {
            //lockGuard lockguard(&mutex); 锁的封装 详细见上篇博文
            pthread_mutex_lock(&mutex);
            if(nullptr==thread_ptr)
            {
                thread_ptr=new ThreadPool<T>(num);
            }
            pthread_mutex_unlock(&mutex);
        }
        return thread_ptr;
    }
private:
    std::vector<Thread*> threads_;
    int num_;
    std::queue<T> task_queue_;
    pthread_mutex_t lock;//保护临界区(任务队列)的一把锁
    pthread_cond_t cond;

    static ThreadPool<T>* thread_ptr;//静态成员 在类外初始化
    static pthread_mutex_t mutex ;//静态成员 在类外初始化
};
template<class T>
ThreadPool<T>* ThreadPool<T>::thread_ptr =nullptr;
template<class T>
pthread_mutex_t ThredPool<T>::mutex=PTHREAD_MUTEX_INITIALIZER;//静态的锁不需要手动释放

 问题:

为什么要判断两次呢?

其他线程想要调用该函数时,如果没有最外层的判断就一定会进行申请锁、释放锁的操作。这样写可以有效减少未来必定要加锁检测的问题

2.其他常见的各种锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
  • CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
  • 自旋锁,公平锁,非公平锁?

我们之前学的锁都是悲观锁。

自旋锁:本质就是通过不断的检测状态(一直打电话催促),来进行资源是否就绪的方案。

(互斥锁:只检测一次,临界资源没就绪就直接挂起。)

什么时候使用自旋锁?临界资源就绪的时间短时(是一个相对的概念,取决于场景的容忍度)。

自旋锁的使用

#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t* lock,int pshared);
int pthread_spin_destroy(pthread_spinlock_t* lock);

无论是挂起等待还是自旋本身都是pthread库帮我们做的,我们只需要使用接口。

#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t* lock);
int pthread_spin_trylock(pthread_spinlock_t* lock); //自旋一次 不成功就返回

 3.读者写者 与消费者生产者的区别

读写锁
公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。

写独占,读共享,读锁优先级高。

区别:

消费者会取走数据,所以消费者消费者之间是互斥关系。二读者写者是共享关系。

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值