线程安全的单例模式其他各种常见的锁

什么是单例模式

单例模式是一种 " 经典的 , 常用的 , 常考的 " 设计模式
什么是设计模式
IT 行业这么火 , 涌入的人很多 . 俗话说林子大了啥鸟都有 . 大佬和菜鸡们两极分化的越来越严重 . 为了让菜鸡们不太拖大 佬的后腿, 于是大佬们针对一些经典的常见的场景 , 给定了一些对应的解决方案 , 这个就是 设计模式。
单例模式的特点
某些类 , 只应该具有一个对象 ( 实例 ), 就称之为单例 .
例如一个男人只能有一个媳妇 .
在很多服务器开发场景中 , 经常需要让服务器加载很多的数据 ( 上百 G) 到内存中 . 此时往往要用一个单例的类来管理这 些数据.
饿汉实现方式和懒汉实现方式
[ 洗完的例子 ]
吃完饭 , 立刻洗碗 , 这种就是饿汉方式 . 因为下一顿吃的时候可以立刻拿着碗就能吃饭 .
吃完饭 , 先把碗放下 , 然后下一顿饭用到这个碗了再洗碗 , 就是懒汉方式 .
懒汉方式最核心的思想是 " 延时加载 ". 从而能够优化服务器的启动速度 .
饿汉方式实现单例模式
template <typename T>
class Singleton {
static T data;
public:
static T* GetInstance() {
return &data;
}
};

只要通过 Singleton 这个包装类来使用 T 对象 , 则一个进程中只有一个 T 对象的实例
懒汉方式实现单例模式
template <typename T>
class Singleton {
static T* inst;
public:
static T* GetInstance() {
if (inst == NULL) {
inst = new T();
}
return inst;
}
};

 

存在一个严重的问题 , 线程不安全 .
第一次调用 GetInstance 的时候 , 如果两个线程同时调用 , 可能会创建出两份 T 对象的实例 .
但是后续再次调用 , 就没有问题了 .
懒汉方式实现单例模式 ( 线程安全版本 )
#include <iostream>
#include <queue>
#include <pthread.h>

namespace ns_threadpool
{
    template <class T>
    class PthreadPool
    {
    public:
        PthreadPool(int num = 5)
            : _num(num)
        {
            pthread_mutex_init(&_mutex, nullptr);
            pthread_cond_init(&_cond, nullptr);
        }
        static PthreadPool<T> * GetInstace()
        {
            static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
            if (_ins == nullptr)   //双判断提高效率
            {
                pthread_mutex_lock(&lock);
                if (_ins == nullptr)
                {
                    _ins = new PthreadPool<T>();
                    _ins->InitPthreadPool();
                    std::cout << "首次加载对象" << std::endl;
                }
                pthread_mutex_unlock(&lock);
            }

            return _ins;
        }
        static void *Rountine(void *args) //因为在类中所以必须要是静态成员,因为有this指针
        {
            pthread_detach(pthread_self());
            PthreadPool<T> *q = (PthreadPool<T> *)args;
            for (;;)
            {
                q->Lock();
                while (q->IsEmpty())
                {
                    q->Wait();
                }
                T t;
                q->PopTask(&t);
                q->Unlock();

                t();
            }
        }
        void InitPthreadPool()
        {
            pthread_t tid;
            for (size_t i = 0; i < _num; ++i)
            {
                pthread_create(&tid, nullptr, Rountine, (void *)this);
            }
        }
        void PushTask(const T &in)
        {
            Lock();
            _task_queue.push(in);
            Unlock();

            WackUp();
        }
        ~PthreadPool()
        {
            pthread_mutex_destroy(&_mutex);
            pthread_cond_destroy(&_cond);
        }

    private:
        void Lock()
        {
            pthread_mutex_lock(&_mutex);
        }
        void Unlock()
        {
            pthread_mutex_unlock(&_mutex);
        }
        void PopTask(T *out)
        {
            *out = _task_queue.front();
            _task_queue.pop();
        }
        bool IsEmpty()
        {
            return _task_queue.empty();
        }
        void Wait()
        {
            pthread_cond_wait(&_cond, &_mutex);
        }
        void WackUp()
        {
            pthread_cond_signal(&_cond);
        }

    private:
        std::queue<T> _task_queue;
        int _num;
        pthread_mutex_t _mutex;
        pthread_cond_t _cond;
        static PthreadPool<T>* _ins;
    };

    template <class T>
    PthreadPool<T>* PthreadPool<T>::_ins = nullptr;
}
注意事项 :
1. 加锁解锁的位置
2. 双重 if 判定 , 避免不必要的锁竞争
其他常见的各种锁
悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前, 会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS 操作。
CAS 操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不
等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
自旋锁,公平锁,非公平锁?

悲观锁前期我们写的代码全是悲观锁举个简单的例子:

 

小李来找小张去学习,但是小张告诉小李他现在给女朋友打电话,要很久,那么小李说那好吧,我先去网吧玩一会,等你好了我在过来。等过了一个1小时,小张告诉小李我好了,小李就从网吧过来和小李一起学习。一种情况可以理解为悲观锁。

小李来找小张去学习,小张说等我1分钟我马上下来,过了1分钟小张还没下来,小李就打电话问还要多久,小李一直打电话问。这中情况可以理解为自旋锁。

可以看出悲观锁是有成本的,小李需要在网络和小张来回走,但是自旋锁也有成本会一直询问,小张是否下来了。 

初始化

  #include <pthread.h>

       int pthread_spin_destroy(pthread_spinlock_t *lock);
       int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

销毁

 #include <pthread.h>

       int pthread_spin_destroy(pthread_spinlock_t *lock);
       int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

加锁

#include <pthread.h>

       int pthread_spin_lock(pthread_spinlock_t *lock);
       int pthread_spin_trylock(pthread_spinlock_t *lock);

解锁

#include <pthread.h>

       int pthread_spin_unlock(pthread_spinlock_t *lock);

可见跟我们平时用的锁机会没什么差别。我就用自旋锁来实现一个抢票,效果与之前一样

#include<iostream>
#include<unistd.h>
#include<pthread.h>

#define NUM 5

pthread_spinlock_t spin;
int tickets = 1000;
void *run(void *args)
{
    while(true)
    {
        pthread_spin_lock(&spin);
        std::cout<<pthread_self()<<"我枪到的票是:"<<tickets<<std::endl;
        tickets--;
        pthread_spin_unlock(&spin);
        sleep(1);
    }
}
int main()
{
    pthread_t tid[NUM];
    pthread_spin_init(&spin,PTHREAD_PROCESS_SHARED);
    for(size_t i = 0; i< NUM; ++i)
    {
        pthread_create(tid+i,nullptr,run,nullptr);
    }
    
    pthread_spin_destroy(&spin);
    for(size_t i = 0; i<NUM; ++i)
    {
        pthread_join(tid[i],nullptr);
    }
}

乐观锁:我将会mysql中详细讲解。

读者写者问题

读写锁
在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的 机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地 降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。

 

 读写锁接口


int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);
 /*
pref 共有 3 种选择 
PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况 
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和 PTHREAD_RWLOCK_PREFER_READER_NP 一致 
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁 */

初始化

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);

销毁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

加锁和解锁

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); 
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值