【Linux系统编程】第五十弹---构建高效单例模式线程池、详解线程安全与可重入性、解析死锁与避免策略,以及STL与智能指针的线程安全性探究

【Linux系统编程】第五十弹---构建高效单例模式线程池、详解线程安全与可重入性、解析死锁与避免策略,以及STL与智能指针的线程安全性探究

✨个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】

目录

1、将日志加到线程池

1.1、Thread类

1.2、ThreadPool类

1.2.1、HandlerTask()

1.2.2、其他公有成员函数

1.3、主函数

2、单例版线程池

2.1、私有成员函数

2.2、获取对象函数

2.2.1、不加锁版本

2.2.2、加锁版本

3、可重入VS线程安全

3.1、概念

3.2、常见的线程不安全的情况

3.3、常见的线程安全的情况

3.4、常见不可重入的情况

3.5、常见可重入的情况

3.6、可重入与线程安全联系

3.7、可重入与线程安全区别

4、常见锁概念

4.1、死锁

4.2、死锁四个必要条件

4.3、避免死锁

4.4、避免死锁算法

5、STL,智能指针和线程安全


1、将日志加到线程池

1.1、Thread类

此处使用日志来打印消息,因此需要将Thread类的std::cout打印信息删除掉!!!
#pragma once #include <iostream> #include <string> #include <functional> #include <pthread.h> namespace ThreadMoudle { // typedef std::function<void()> func_t; using func_t = std::function<void(const std::string&)>; class Thread { public: void Excute() { _isrunning = true; _func(_name); _isrunning = false; } public: Thread(const std::string& name,func_t func):_name(name),_func(func) {} // 新线程执行该方法 static void* ThreadRoutine(void* args) { Thread* self = static_cast<Thread*>(args); self->Excute(); return nullptr; } std::string Status() { if(_isrunning) return "running"; else return "sleep"; } bool Start() { // ::使用库函数接口,直接使用ThreadRoutine会报错,因为成员函数有隐含this指针 + static int n = ::pthread_create(&_tid,nullptr,ThreadRoutine,this); if(n != 0) return false; return true; } void Stop() { if(_isrunning) { ::pthread_cancel(_tid); _isrunning = false; } } std::string Name() { return _name; } void Join() { ::pthread_join(_tid,nullptr); } ~Thread() {} private: std::string _name; pthread_t _tid; bool _isrunning; func_t _func; // 线程要执行的回调函数 }; }

1.2、ThreadPool类

1.2.1、HandlerTask()

将std::cout部分改为LOG打印!!!
// 处理任务,从任务队列取任务,加锁 void HandlerTask(const std::string &name) { while (true) { // 1.取任务 LockQueue(); // 队列为空且运行则休眠 while (IsEmpty() && _isrunning) // if? { _sleep_thread_num++; // 防止阻塞,因为一开始休眠线程数为0 LOG(INFO,"%s thread sleep begin!\n",name.c_str()); Sleep(); LOG(INFO,"%s thread wakeup!\n",name.c_str()); _sleep_thread_num--; } // 判定一种情况,为空且不运行则退出 if (IsEmpty() && !_isrunning) { // std::cout << name << " quit" << std::endl; LOG(INFO,"%s thread quit!\n",name.c_str()); UnlockQueue(); break; } // 有任务 T t = _task_queue.front(); _task_queue.pop(); UnlockQueue(); // 2.处理任务 t(); // 处理任务(只属于自己线程),此处不用/不能在临界区处理 // std::cout << name << ":" << t.result() << std::endl; LOG(DEBUG,"hander task done,task is : %s\n",t.result().c_str()); } }

1.2.2、其他公有成员函数

此处只展示需要修改的部分!!!
void Init() { func_t func = std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1); // 绑定 for (int i = 0; i < _thread_num; i++) { std::string threadname = "thread-" + std::to_string(i + 1); // _threads.emplace_back(threadname,test/*TODO*/); // 按照构造函数尾插对象 _threads.emplace_back(threadname, func); // _threads.emplace_back(threadname, HandlerTask); LOG(DEBUG,"construct thread %s done,init success\n",threadname.c_str()); } } void Start() { _isrunning = true; for (auto &thread : _threads) { LOG(DEBUG,"start thread %s done.\n",thread.Name().c_str()); thread.Start(); } } void Stop() { LockQueue(); _isrunning = false; WakeupAll(); // 防止有线程在等待 UnlockQueue(); LOG(INFO,"thread pool stop done!\n"); }

1.3、主函数

主函数执行5次之后,停止线程池!!!
// 测试日志版线程池 int main() { ThreadPool<Task>* tp = new ThreadPool<Task>(); tp->Init(); tp->Start(); int cnt = 5; // 执行5次循环结束 while(cnt) { Task t(1,1); tp->Equeue(t); LOG(INFO,"equeue a task,%s\n",t.debug().c_str()); cnt--; sleep(1); } // 新线程停止 tp->Stop(); LOG(INFO,"thread pool stop\n"); // 5秒后主线程结束 sleep(5); return 0; }

运行结果 

2、单例版线程池

1、单例模式特点:某些类, 只应该具有一个对象(实例), 就称之为单例.

2、单例版(懒汉模式)线程池需要加两个成员变量静态线程池指针(获取类对象地址),静态互斥锁(解决多线程获取多个对象问题)!!!

注意:静态成员变量需要在类外初始化!!!

2.1、私有成员函数

为了实现单例模式需要将构造函数私有禁止拷贝构造和赋值操作符重载,并将初始化和启动线程池函数私有!!!
private: // 单例模式构造函数私有 ThreadPool(int thread_num = gdefaultnum) : _thread_num(thread_num), _isrunning(false), _sleep_thread_num(0) { pthread_mutex_init(&_mutex, nullptr); pthread_cond_init(&_cond, nullptr); } void Init() { func_t func = std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1); // 绑定 for (int i = 0; i < _thread_num; i++) { std::string threadname = "thread-" + std::to_string(i + 1); // _threads.emplace_back(threadname,test/*TODO*/); // 按照构造函数尾插对象 _threads.emplace_back(threadname, func); // _threads.emplace_back(threadname, HandlerTask); LOG(DEBUG, "construct thread %s done,init success\n", threadname.c_str()); } } void Start() { _isrunning = true; for (auto &thread : _threads) { LOG(DEBUG, "start thread %s done.\n", thread.Name().c_str()); thread.Start(); } } ThreadPool(const ThreadPool<T> &) = delete; // 禁止拷贝构造 void operator=(const ThreadPool<T> &) = delete; // 禁止赋值重载

2.2、获取对象函数

类外初始化静态成员

// 静态成员类外初始化 template <typename T> ThreadPool<T> *ThreadPool<T>::_tp = nullptr; template <typename T> pthread_mutex_t ThreadPool<T>::_sig_mutex = PTHREAD_MUTEX_INITIALIZER;

2.2.1、不加锁版本

单进程单线程可以不用加锁!!!
// 单线程 static ThreadPool<T> *GetInstance() { if (_tp == nullptr) { LOG(INFO, "create thread pool\n"); _tp = new ThreadPool(); _tp->Init(); _tp->Start(); } else { LOG(INFO, "get thread pool\n"); } return _tp; }

运行结果

测试是否为单例 

int main() { // 测试是否为单例 ThreadPool<Task>* tp1 = new ThreadPool<Task>(); ThreadPool<Task> tp2 = *(ThreadPool<Task>::GetInstance()); return 0; }

运行结果 

2.2.2、加锁版本

多线程可能会有创建多个对象的情况,需加锁解决!!!
static ThreadPool<T> *GetInstance() { // 只有第一次需要加锁,减少锁的竞争 if (_tp == nullptr) { LockGuard lockguard(&_sig_mutex); if (_tp == nullptr) { LOG(INFO, "create thread pool\n"); _tp = new ThreadPool(); _tp->Init(); _tp->Start(); } else { LOG(INFO, "get thread pool\n"); } } return _tp; }

3、可重入VS线程安全

3.1、概念

  • 线程安全多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。
  • 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

3.2、常见的线程不安全的情况

  • 不保护共享变量的函数
  • 函数状态随着被调用,状态发生变化的函数
  • 返回指向静态变量指针的函数
  • 调用线程不安全函数的函数

3.3、常见的线程安全的情况

  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
  • 类或者接口对于线程来说都是原子操作
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性

3.4、常见不可重入的情况

  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
  • 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
  • 可重入函数体内使用了静态的数据结构

3.5、常见可重入的情况

  • 不使用全局变量或静态变量
  • 不使用用 malloc 或者 new 开辟出的空间
  • 不调用不可重入函数
  • 不返回静态或全局数据,所有数据都有函数的调用者提供
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

3.6、可重入与线程安全联系

  • 函数是可重入的,那就是线程安全的
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的

3.7、可重入与线程安全区别

  • 可重入函数是线程安全函数的一种
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的
  • 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的

4、常见锁概念

4.1、死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。

4.2、死锁四个必要条件

死锁就一定会出现下面的四个条件,但是出现下面四个条件不代表死锁!!!
  • 互斥条件:一个资源每次只能被一个执行流使用
  • 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

4.3、避免死锁

  • 破坏死锁的四个必要条件
  • 加锁顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配

4.4、避免死锁算法

  • 死锁检测算法(了解)
  • 银行家算法(了解)

5、STL,智能指针和线程安全

STL 中的容器是否是线程安全的?

不是。原因是:

1、STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.
2、而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如 hash 表的锁表和锁桶).
因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全.

智能指针是否是线程安全的?

1、对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题.
2、对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.

Read more

「源力觉醒 创作者计划」_文心大模型4.5系列开源模型,意味着什么?对开发者、对行业生态有何影响?

「源力觉醒 创作者计划」_文心大模型4.5系列开源模型,意味着什么?对开发者、对行业生态有何影响?

前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 * 「源力觉醒 创作者计划」_文心大模型4.5系列开源模型,意味着什么?对开发者、对行业生态有何影响? * 开发者的 "技术红利" 与 "创新跳板" * 降低开发成本:从 "军备竞赛" 到 "轻量启动" * 提供学习资源:从 "黑箱盲猜" 到 "白盒解剖" * 激发创新活力:从 "闭门造车" 到

By Ne0inhk

码云(Gitee)代码推送全流程:实操学习心得

一、目录 1. 学习背景与核心目标 2. 代码推送核心步骤(附精细化实操) 3. 进阶操作:分支管理与冲突解决 4. 实操技巧与避坑指南(新增典型案例) 5. 自动化优化:脚本简化推送流程 6. 学习总结与思维提升 二、学习背景与核心目标 在国产化开发协作场景中,码云(Gitee)作为基于 Git 的本土代码托管平台,是开发者实现代码版本管理、跨团队协作的核心工具。本次学习聚焦 “工作端代码推送到码云端” 全流程,核心目标不仅是掌握基础推送操作,更要实现:① 标准化配置 Git 环境,适配 KylinOS 等国产系统;② 精准处理分支管理与代码冲突;③ 优化推送流程,提升开发效率,最终形成可复用的代码管理规范。 三、代码推送核心步骤(附精细化实操) (一)前期准备:环境深度配置 1.

By Ne0inhk
【GitHub周榜】WrenAI:开源SQL AI代理,让Text-to-SQL轻松实现,开启自然语言与数据交互新时代

【GitHub周榜】WrenAI:开源SQL AI代理,让Text-to-SQL轻松实现,开启自然语言与数据交互新时代

系列篇章💥 No.文章1【GitHub周榜】OpenHands:AI赋能,软件开发效率狂飙10倍2【GitHub周榜】Agno:快速构建多模态智能体的轻量级框架,开发提速 10000 倍3【GitHub周榜】WrenAI:开源SQL AI代理,让Text-to-SQL轻松实现,开启自然语言与数据交互新时代 目录 * 系列篇章💥 * 前言 * 一、项目概述 * 二、主要功能 * 1、多语言自然对话 * 2、智能数据探索 * 3、语义索引系统 * 4、上下文 SQL 生成 * 5、无代码数据分析 * 6、AI 驱动可视化 * 7、数据导出集成 * 8、安全性保障 * 三、技术原理 * 四、应用场景 * 1、

By Ne0inhk

Qwen3-TTS开源大模型效果展示:方言语音合成+上下文感知韵律生成案例

Qwen3-TTS开源大模型效果展示:方言语音合成+上下文感知韵律生成案例 1. 为什么这次语音合成让人眼前一亮? 你有没有试过让AI读一段带方言味的文案?比如“侬好呀,今朝天气老灵额”,或者“俺们村后山的苹果,又脆又甜!”——以前的语音合成工具要么念得像机器人背课文,要么干脆把方言词读成普通话腔调,听着别扭又失真。 Qwen3-TTS-12Hz-1.7B-VoiceDesign 这次不一样。它不是简单地“换音色”,而是真正理解了语言背后的节奏、情绪和地域味道。我第一次听到它合成上海话时,下意识停下手头工作——那句“阿拉今朝勿出门,困觉最适意”里的“阿拉”“困觉”发音自然,语调上扬带点慵懒,连“勿”字的轻声弱化都恰到好处,完全不像AI,倒像隔壁弄堂里刚买完小笼包回来的阿姨随口一说。 这不是靠堆参数堆出来的效果,而是模型从训练数据里“听懂”了方言的呼吸感:哪里该拖长音,哪里该突然收住,哪句话表面平静底下藏着调侃……它甚至能根据上下文自动调整。比如同样一句“你再说一遍?”,在客服场景里是礼貌确认,在朋友吵架时就变成带着火气的质问——Qwen3-TTS

By Ne0inhk