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

什么是单例模式

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

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 分钟前
算法3.双指针法-快乐数
c++·笔记·学习·算法
福大大架构师每日一题22 分钟前
excelize v2.11.1发布:Go 1.26.0起步,16项安全修复与嵌入对象能力全面升级
开发语言·安全·golang
Linux界最忧郁之人35 分钟前
华为云ECS&IMS&AS实战|弹性云服务器、私有镜像、弹性伸缩完整实验
linux·运维·服务器·centos·华为云·openstack
yunlaodacom42 分钟前
做异地灾备,华为云国际站 OBS 跨地域复制该怎么配置?
安全·华为云·云计算
eggwyw1 小时前
C++相交链表和反转链表详解
开发语言·c++
杜 硕1 小时前
从零开始的C++生活——第六讲——02
c++·算法·生活
whyutianict_vv1 小时前
零基础转 AI 运维:投入清单、技术路线与核对表
linux·运维·人工智能·ai
无言听风雨丶1 小时前
systemd 与信号 / 僵尸进程:Linux 进程管理的底层逻辑与排错
linux·服务器·网络
江湖中的阿龙1 小时前
Oracle 紧急安全警报:Java 9.1 分漏洞 CVE-2026-91403,撤销对 eMudhra 风险证书的信任
java·安全·oracle
小超人诞生日记1 小时前
C++ STL 反向迭代器:从源码框架到适配器实现
c++·算法