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

什么是单例模式

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

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文件和上面那篇文章的是一样的

相关推荐
前端Hardy15 小时前
2026 VSCode 插件终极清单!20 + 款必装神器,开发效率直接封神
visual studio code
Hical6115 小时前
C++26 前瞻心得:下一代 C++ 最值得期待的特性
c++
悲伤小伞15 小时前
Linux_传输层协议TCP详解
linux·网络·c++·网络协议·tcp/ip
笨笨饿15 小时前
#72_聊聊I2C以及他们的变体
linux·c语言·网络·stm32·单片机·算法·个人开发
ulias21215 小时前
leetcode热题 - 6
linux·算法·leetcode
Frank_refuel16 小时前
C++之STL->string类的使用和实现
java·开发语言·c++
fpcc16 小时前
跟我学C++中级篇—Linux文件读写的分析
linux·c++
Bruce_Liuxiaowei16 小时前
从霍尔木兹到信息空间:现代冲突中的媒体行业安全新命题
人工智能·安全·系统安全·媒体
techdashen16 小时前
4 个字节拿到 root 权限:Linux 内核漏洞“Copy Fail“与 Cloudflare 的应急处置全记录
linux·网络·安全
wanhengidc16 小时前
算力服务器的优势都有哪些?
大数据·运维·服务器·网络·人工智能·安全·智能手机