当前位置: 首页 > news >正文

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

什么是单例模式
单例模式是一种 "经典的, 常用的, 常考的" 设计模式.
什么是设计模式
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 对象的实例.
但是后续再次调用, 就没有问题了.

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

线程池

Threadpool.hpp

#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

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

相关文章:

  • C\C++内存管理详解
  • 基于Jeecg-boot开发系统--后端篇
  • Linux-df命令使用方法
  • Vue3 路由传参:玩转 params,让页面交互更流畅!
  • 设计模式-PIMPL 模式
  • RNN模型学习
  • C++ 排序算法
  • 自适应查询优化(Adaptive Query Optimization, AQO)技术简介
  • react crash course 2024(5) useState钩子
  • DPDK 简易应用开发之路 2:UDP数据包发送及实现
  • 记录打鼾软件
  • 2024最新版 Tuxera NTFS for Mac 2023绿色版图文安装教程
  • 基于单片机的智能温控风扇系统的设计
  • llamafactory0.9.0微调qwen2.5
  • 深度学习驱动智能超材料设计与应用
  • “寒冬”下的金三银四跳槽季来了,帮你客观分析一下局面
  • 【刷算法】求1+2+3+...+n
  • Apache的80端口被占用以及访问时报错403
  • Babel配置的不完全指南
  • docker python 配置
  • EOS是什么
  • flask接收请求并推入栈
  • IDEA 插件开发入门教程
  • learning koa2.x
  • python 装饰器(一)
  • Redis字符串类型内部编码剖析
  • ubuntu 下nginx安装 并支持https协议
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • windows-nginx-https-本地配置
  • 分布式事物理论与实践
  • 构建工具 - 收藏集 - 掘金
  • 检测对象或数组
  • 前端面试总结(at, md)
  • 深度解析利用ES6进行Promise封装总结
  • 视频flv转mp4最快的几种方法(就是不用格式工厂)
  • 曜石科技宣布获得千万级天使轮投资,全方面布局电竞产业链 ...
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • (30)数组元素和与数字和的绝对差
  • (4.10~4.16)
  • (5)STL算法之复制
  • (二)linux使用docker容器运行mysql
  • (附源码)计算机毕业设计ssm电影分享网站
  • (每日一问)基础知识:堆与栈的区别
  • (四) Graphivz 颜色选择
  • (转) Face-Resources
  • ***php进行支付宝开发中return_url和notify_url的区别分析
  • . NET自动找可写目录
  • .NET 6 在已知拓扑路径的情况下使用 Dijkstra,A*算法搜索最短路径
  • .NET MAUI Sqlite数据库操作(二)异步初始化方法
  • .NET/C# 判断某个类是否是泛型类型或泛型接口的子类型
  • .NET/C# 中设置当发生某个特定异常时进入断点(不借助 Visual Studio 的纯代码实现)
  • .net6 core Worker Service项目,使用Exchange Web Services (EWS) 分页获取电子邮件收件箱列表,邮件信息字段
  • .netcore如何运行环境安装到Linux服务器
  • .NET关于 跳过SSL中遇到的问题
  • .NET企业级应用架构设计系列之结尾篇