【线程】线程安全的单例模式

什么是单例模式

单例模式是一种 "经典的, 常用的, 常考的" 设计模式.
什么是设计模式

IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是设计模式

单例模式的特点

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

例如一个男人只能有一个媳妇.

在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据.

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

洗完的例子

吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭.

吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式.

懒汉方式最核心的思想是 "延时加载". 从而能够优化服务器的启动速度.

饿汉方式实现单例模式

cpp 复制代码
template <typename T>
class Singleton {
    static T data;
public:
    static T* GetInstance() {
    return &data;
}
};

只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例

懒汉方式实现单例模式

cpp 复制代码
template <typename T>
class Singleton {
    static T* inst;
public:
    static T* GetInstance() {
    if (inst == NULL) {
    inst = new T();
    }
    return inst;
}
};

存在一个严重的问题, 线程不安全.

第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例.

但是后续再次调用, 就没有问题了.

下面把这一篇文章的线程池改为懒汉模式的单例,改动了构造和析构函数,防止构造出第二个对象,多了一个静态的类的指针成员,来获取这个类

线程池

Threadpool.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include "task.hpp"

using namespace std;

struct ThreadInfo
{
    pthread_t tid;
    string name;
};

template <class T>
class ThreadPool
{
public:
    void Lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }
    void Threadsleep()
    {
        pthread_cond_wait(&cond_, &mutex_);
    }
    void Wakeup()
    {
        pthread_cond_signal(&cond_);
    }
    bool IsQueueEmpty()
    {
        return tasks_.empty();
    }

    string GetThreadName(pthread_t tid)
    {
        for (const auto &ti : threads_)
        {
            if (ti.tid == tid)
                return ti.name;
        }
        return "None";
    }

public:
    static void *HandlerTask(void *args) // 必须定义为静态函数,这样才不会传this指针过来,才不会导致函数不匹配问题
    {
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        string name = tp->GetThreadName(pthread_self());
        while (true)
        {
            tp->Lock();
            while (tp->IsQueueEmpty())
            {
                tp->Threadsleep();
            }
            // 消费任务
            T t = tp->Pop();
            tp->Unlock();
            // 处理任务
            t();
            cout << name << " run, " << "result: " << t.GetResult() << endl;
        }
        return nullptr;
    }

    void Start()
    {
        int size = threads_.size();
        for (int i = 0; i < size; i++)
        {
            threads_[i].name = "thread-" + to_string(i + 1);
            pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this); // 传入this指针,使静态函数可以访问类内成员和函数
        }
    }
    T Pop()
    {
        T out = tasks_.front();
        tasks_.pop();
        return out;
    }
    void Push(const T &in)
    {
        // 需要加锁
        Lock();
        tasks_.push(in);
        Wakeup();
        Unlock();
    }
    static ThreadPool<T> *GetInstance()
    {
        if (tp_ == nullptr)//???假如已经是空指针了,就不用申请锁进去什么事也没干又释放锁,降低效率
        {
            pthread_mutex_lock(&lock_);//tp_是临界资源需要锁的保护
            if (tp_ == nullptr)
            {
                std::cout << "log: singleton create done first!" << std::endl;
                tp_ = new ThreadPool<T>;
            }
            pthread_mutex_unlock(&lock_);
        }
        return tp_;
    }

private:
    // 把构造析构私有化,和没有必要的拷贝构造和赋值重载delete,防止定义出第二个对象
    ThreadPool(int num = 5) : threads_(num)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    ThreadPool(const ThreadPool<T> &tp) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &tp) = delete;

private:
    vector<ThreadInfo> threads_; // 用数组管理创建出来的线程
    queue<T> tasks_;             // 用队列来管理任务

    pthread_mutex_t mutex_;
    pthread_cond_t cond_;
    static ThreadPool<T> *tp_; // 静态成员在类外定义
    static pthread_mutex_t lock_;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;

template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;

main.cc

cpp 复制代码
#include<iostream>
#include<unistd.h>
#include<time.h>
#include"threadpool.hpp"

using namespace std;

int main()
{
    ThreadPool<Task>::GetInstance()->Start();
    while(true)
    {
        //1. 构建任务
        int x = rand() % 10 + 1;
        usleep(10);
        int y = rand() % 5;
        char op = opers[rand()%opers.size()];

        Task t(x, y, op);
        ThreadPool<Task>::GetInstance()->Push(t);
        //2. 交给线程池处理
        std::cout << "main thread make task: " << t.GetTask() << std::endl;

        sleep(1);
    }

}

Task.hpp文件和上面那篇文章的是一样的

相关推荐
程序猿小三21 分钟前
Linux下基于关键词文件搜索
linux·运维·服务器
wfsec1 小时前
EDI许可证:企业数字化转型的“安全基石”与“信任通行证”
安全
光储圈1 小时前
光伏安全协议-安全责任协议书8篇
网络·安全
虚拟指尖1 小时前
Ubuntu编译安装COLMAP【实测编译成功】
linux·运维·ubuntu
你的人类朋友1 小时前
什么是断言?
前端·后端·安全
Larry_Yanan2 小时前
QML学习笔记(三十四)QML的GroupBox、RadioButton
c++·笔记·qt·学习·ui
FIN66682 小时前
昂瑞微:实现精准突破,攻坚射频“卡脖子”难题
前端·人工智能·安全·前端框架·信息与通信
刘某的Cloud2 小时前
parted磁盘管理
linux·运维·系统·parted
啊?啊?2 小时前
4 解锁 Linux 操作新姿势:man、grep、tar ,创建用户及添加权限等 10 大实用命令详解
linux·服务器·实用指令
程序员老舅2 小时前
干货|腾讯 Linux C/C++ 后端开发岗面试
linux·c语言·c++·编程·大厂面试题