C++标准线程库实现优雅退出的方式
目录
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_BEGIN
class 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_BEGIN
IThread::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 loop
post_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++标准线程库中的线程优雅退出。