目录
1.通过设置共享退出标记
2.使用std::jthread创建线程
3.定义消息类型的方式
4.注意事项
1.通过设置共享退出标记
定义一个退出变量bool stop = false;
表示线程是否应该停止。在主线程中设置标记stop=true,然后join一直等待,然后线程循环检测到stop是否为true,为true则表示线程该退出了。示例代码如下:
IThread.h
#ifndef _I_THREAD_H_
#define _I_THREAD_H_
#include "LinkGlobal.h"
#include <Thread>
#include <memory>LINK_CORE_NAMESPACE_BEGINclass IThread
{
public:explicit IThread();virtual ~IThread() = default;public:void start();void join();protected:virtual void run() = 0;private:std::unique_ptr<std::thread> m_thread;bool m_bStarted;
};LINK_CORE_NAMESPACE_END#endif
IThread.cpp
#include "IThread.h"LINK_CORE_NAMESPACE_BEGINIThread::IThread():m_bStarted(false), m_thread(nullptr)
{}void IThread::start()
{if (m_bStarted) {join();return;}m_thread.reset(new std::thread(&IThread::run, this));m_bStarted = true;
}void IThread::join()
{if (m_bStarted && m_thread) {if (m_thread->joinable()) {m_thread->join();}}m_bStarted = false;
}LINK_CORE_NAMESPACE_END
QueryDataCmdThread.h
#pragma once
#include "IThread.h"
#include "DataType.h"
#include <string>
#include <QByteArray>
using namespace xyLinkCore;class CQueryDataCmdThread : public IThread
{
public:CQueryDataCmdThread (PUInt64 chaissID, PUInt64 signalID);virtual ~CQueryDataCmdThread ();public:int start();int stop();private:void run() override;void sendQueryCmd();void encodeData();private:bool m_bStop;PUInt64 m_chaissID;PUInt64 m_signalID;bool m_status;std::string m_waveName;QByteArray m_data;
};
QueryDataCmdThread.cpp
#include "QueryDataCmdThread.h"
#include "HardwareDataProcCenter.h"CQueryDataCmdThread::CQueryDataCmdThread(PUInt64 chaissID, PUInt64 signalID): m_bStop(false), m_chaissID(chaissID), m_signalID(signalID), m_status(false)
{encodeData();
}CQueryDataCmdThread::~CQueryDataCmdThread()
{stop();
}int CQueryDataCmdThread::start()
{m_bStop = false;m_status = false;IThread::start();return 1;
}
int CTTNTQueryDataCmdThread::stop()
{if (m_status) {return 1;}m_bStop = true;IThread::join();return 1;
}void CQueryDataCmdThread::run()
{while (true) {//[1]if (m_bStop) {m_status = true;break;}//[2]sendQueryCmd();//[3]std::this_thread::sleep_for(std::chrono::milliseconds(200));}
}void CQueryDataCmdThread::encodeData()
{//...
}void CTTNTQueryDataCmdThread::sendQueryCmd()
{//...
}
2.使用std::jthread创建线程
std::jthread
是 C++20 标准库中引入的一个新特性,它代表了一个可加入的线程(joinable thread),它确保了线程在其生命周期内始终运行一个特定的任务(即一个函数对象、lambda 表达式或者可调用对象)。std::jthread
的设计旨在简化多线程编程中的一些常见模式,特别是那些需要确保线程在其生命周期内始终运行的任务。
std::jthread
在std::thread
基础上,增加了能够主动取消或停止线程执行的新特性。与 std::thread
相比,std::jthread
具有异常安全的线程终止流程,并且在大多数情况下可以替换它,只需很少或无需更改代码。
示例代码如下:
#include <iostream>
#include <chrono>
#include <thread>// 使用 std::jthread 运行的函数
void task(std::stop_token stoken) {while (!stoken.stop_requested()) {std::cout << "任务正在运行..." << std::endl;// 模拟一些工作std::this_thread::sleep_for(std::chrono::seconds(1));}std::cout << "任务已收到停止请求,现在停止运行。" << std::endl;
}int main() {// 创建 std::jthread,自动处理停止令牌std::jthread worker(task);// 模拟主线程运行一段时间后需要停止子线程std::this_thread::sleep_for(std::chrono::seconds(5));std::cout << "主线程请求停止子线程..." << std::endl;// 触发停止请求worker.request_stop();// std::jthread 在析构时自动加入return 0;
}
有了std::thread,为什么还需要引入std::jthread?-CSDN博客
3.定义消息类型的方式
spdlog一个非常好用的C++日志库(五): 源码分析之线程池thread_pool_spdlog源码分析-CSDN博客
在spdlog的线程池thread_pool源码分析一文中,首先定义了消息类型:
enum class async_msg_type
{log, //普通日志消息flush, //冲刷日志消息到目标(sink)terminate //终止线程池子线程(工作线程)
};
接下来就是在thread_pool的析构函数出提交一条async_msg_type::terminate消息,如下面代码:
SPDLOG_INLINE thread_pool::~thread_pool()
{// 析构函数不要抛出异常, 但释放线程池资源资源可能发生异常, 因此内部捕获并处理SPDLOG_TRY{for (size_t i = 0; i < threads_.size(); i++) {// terminate thread looppost_async_msg_(async_msg(async_msg_type::terminate), async_overflow_policy::block);}for (auto & t : threads_) {t.join();}}SPDLOG_CATCH_STD
}
最后在单个线程循环中,检测到async_msg_type::terminate消息,就退出线程,代码如下:
// 子线程循环
void SPDLOG_INLINE thread_pool::worker_loop_()
{while (process_next_msg_()) {}
}// process next message in the queue
// return true if this thread should still be active (while no terminate msg
// was received)
bool SPDLOG_INLINE thread_pool::process_next_msg_()
{async_msg incoming_async_msg;bool dequeued = q_.dequeue_for(incoming_async_msg, std::chrono::seconds(10)); // 从环形缓冲区取出数据if (!dequeued){return true;}// 成功取出一条数据存作为异步消息, 根据消息类型分类处理switch (incoming_async_msg.msg_type){case async_msg_type::log: { // 处理类别为log的异步消息incoming_async_msg.worker_ptr->backend_sink_it_(incoming_async_msg);return true;}case async_msg_type::flush: { // 处理类别为flush的异步消息incoming_async_msg.worker_ptr->backend_flush_();return true;}case async_msg_type::terminate: { // 处理类别为terminate的异步消息return false;}default: {assert(false); // impossible except exception}}return true;
}
4.注意事项
- 确保在发送停止信号后,主线程等待工作线程实际退出(使用
join
或detach
,但通常使用join
以确保资源被正确释放)。 - 在线程函数内部,确保在退出前释放所有分配的资源,包括动态内存、文件句柄、网络连接等。
- 避免在多个线程之间共享可变数据,除非使用了适当的同步机制(如互斥锁、读写锁等)。
通过上述方法,你可以实现C++标准线程库中的线程优雅退出。