多线程/std::thread线程退出方式详解

文章目录

  • 概述
  • 不 join 也不 detach
  • 执行了detach并不能万事大吉
  • 建议使用 join 函数

概述

这里默认你已经了解 std::thread 类的基本使用,和WinAPI多线程编程中 “如何优雅的退出线程” 等相关知识。阅读该文前,建议先看看《多线程 /C++ 11 std::thread 类深入理解和应用实践》 和 《多线程/WinAPI线程退出方式比较分析》这两篇文章。在 函数 join 和 函数 detach 的帮助文档中都讲到,
join(), After a call to this function, the thread object becomes non-joinable and can be destroyed safely.
detach(), After a call to std::thread::detach, the thread object becomes non-joinable and can be destroyed safely.
即在线程对象上调用 join函数或detach函数 后,线程对象便可安全地销毁了,何为安全,真的安全吗?

另外一个问题是,std::thread并未提供,像ExitThread、TerminateThread这样的终止线程的接口,也没有直接提供WaitForSingleObject 类似的等待函数, 当使用 std::thread 进行多线程编程时,似乎只有 “入口函数返回” 这一种方式。另外就是进程退出倒逼线程退出的方式。

不 join 也不 detach

int main()
{
    int interval_child = 1, interval_main = 2;  //s
    //
    std::thread t1 = std::thread([&]() {
        std::this_thread::sleep_for(std::chrono::seconds(interval_child));
        printf("at:%f, t1_entry_func return \n", DalOsTimeSysGetTime());
    });

    //预留时间,等待次线程结束
    std::this_thread::sleep_for(std::chrono::seconds(interval_main));
    //
    printf("at:%f, main end sleep t1.joinable:%d \n", DalOsTimeSysGetTime(), t1.joinable());

    //try {
    //    t1.join();
    //    printf("at:%f, t1.join return \n", DalOsTimeSysGetTime());
    //}
    //catch (const std::exception&) {
    //    std::cout << "any system_error exception \n";
    //}

    //system("pause");  
    
    return 0;
}

上述代码,运行结果如下:
在这里插入图片描述
可以得出,
1、即使线程对象代表的执行线程已经(函数返回)完成,此时对象 t1 依然是 joinable,可加入到其他线程中的。
2、进程退出前,如果没有对 std::thread 对象 t1 执行 join 或 detach 操作,则会触发abort终止进程。
补充,
一般情况下,无法通过异常处理机制捕获导致abort()调用的异常。当调用abort()函数时,理论上,可以通过一些系统相关的底层注册机制拦截或过滤它,我尝试了几种方式都没有成功,这里不再深究。

执行了detach并不能万事大吉

改动detach帮助下的示例程序如下,主要目的在于测试:进程退出后,子线程是否会 继续 完成执行过程。

#include <iostream>       // std::cout
#include <thread>         // std::thread, std::this_thread::sleep_for
#include <chrono>         // std::chrono::seconds
#include <string.h>
#include <fstream>
#include <stdio.h>
#include <windows.h>

using namespace std;

//辅助函数 //写log函数
void WriteLog(const char * format, ...)
{
    char buff[128] = { 0 };
    va_list ap;
    va_start(ap, format);
    vsprintf_s(buff, 128, format, ap);
    va_end(ap);
    //
    std::ofstream outfile("result.txt", std::ios_base::app);
    outfile.write(buff, strlen(buff));
    outfile.close();
}

//辅助函数 //时刻值ms //%f
double DalOsTimeSysGetTime(void)
{
    LARGE_INTEGER nFreq; LARGE_INTEGER nBeginTime;
    QueryPerformanceFrequency(&nFreq);
    QueryPerformanceCounter(&nBeginTime);
    return (double)(nBeginTime.QuadPart * 1000 / (double)nFreq.QuadPart);
}

//定义一个C++类对象
class ClassA
{
public:
    ClassA(int id): m_id(id) {
        WriteLog("CppObject-%d 构造 at %f\n", m_id, DalOsTimeSysGetTime());
        //申请堆内存
        m_pData = new TData();
    }
    ~ClassA() {
        WriteLog("CppObject-%d 析构 at %f\n", m_id, DalOsTimeSysGetTime());
        //释放堆内存
        if (NULL != m_pData) {
            delete m_pData;
            m_pData = NULL;
        }
    }

private:
    struct TData { //数据类
        int a;  int b;
    };

    TData *m_pData = NULL; int m_id = 0;
};

//入口函数
void pause_thread(int n)
{
    //定义在线程栈上的对象
    ClassA aObjectInStack(n);
    //使得指定的次线程睡眠n秒
    std::this_thread::sleep_for(std::chrono::seconds(n));
    //
    WriteLog("thread_%d pause %d seconds, then return at %f \n", n, n, DalOsTimeSysGetTime());
}

int main()
{
    std::ofstream outfile("result.txt", std::ios_base::trunc);
    outfile.close(); //清空日志文件

    std::cout << "Spawning and detaching 3 threads...\n";
    std::thread(pause_thread, 1).detach();
    std::thread(pause_thread, 2).detach();
    std::thread(pause_thread, 4).detach();
    std::cout << "Done spawning threads.\n";

    std::cout << "the main thread will now pause for 3 seconds\n";
    //you can give the detached threads time to finish (but not guaranteed!):
    std::this_thread::sleep_for(std::chrono::seconds(3));

    return 0;
}

//CppObject - 1 构造 at 112975428.942300
//CppObject - 2 构造 at 112975430.304200
//CppObject - 4 构造 at 112975431.947500
//thread_1 pause 1 seconds, then return at 112976440.094500
//CppObject - 1 析构 at 112976441.718200
//thread_2 pause 2 seconds, then return at 112977441.448000
//CppObject - 2 析构 at 112977442.140000
//id==4的线程 入口函数未完成执行,其内的对象未触发析构。

可以看出来,在进程退出后,
1、正在运行的线程入口函数的执行是"戛然而止"的,如果此时pause_thread入口函数内是while循环模式的,则线程将极有可能要死在while单次循环执行过程中,这是危险不优雅的。
2、入口函数若没有执行返回,则不会触发线程函数内对象的析构过程。
3、在 detach 作用下,并不会保证入口函数返回。 进程退出时,如果入口函数已经完成,则没有任何问题。但如果此时入口函数尚在执行过程中(如等待、耗时IO操作等),将与windowsAPI::ExitThread 的使用效果如出一辙。故,在使用detach分离线程后,若不加以控制使得入口函数能保证是可返回的,虽然系统会释放线程栈,但是由于此时析构过程未触发,依然存在m_pData堆内存泄漏的问题。

进程退出,
只要进程不退出(如将主线程使用system(“pause”) 或者 使用while循环睡眠,来保持运行),则detach的次线程便可以一直运行下去(如果它能一直运行下去),这是毋庸置疑的。如果宿主线程(或称MSDN中的calling thread调用线程)不是主线程,而是其他次线程,则线程对象在被detach后更不会退出执行。

建议使用 join 函数

讨论来讨论去,还是建议使用 join函数老实的等待执行线程退出。为此,我们可能需要将线程对象创建为成员变量或全局变量,以能在线程停止函数中调用join函数,实现等待操作。如下:

//在stop函数中利用join等待
void MyThreadStop() {
	m_runningFlag = fale;
	join();
}

平日里我们见到的示例程序大都简单到只是在main函数中创建子线程并直接退出,在main函数中 “同步地” 调用detach或join就完犊了。而实际使用中通常会更 “异步” 一些。如,在Qt环境的事件循环机制下,join的 “异步” 调用可能是:

#mian.cpp

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MianWnd w;
    w.show();
    return a.exec();
}

#mianWnd.h

class MianWnd : public QMainWindow
{
    Q_OBJECT
public:
    MianWnd(QWidget *parent = Q_NULLPTR);
    ~MianWnd();
private:
    //函数入口
    void pause_thread(int n);
    //停止函数
    void MyThreadStop();
private:
    //线程
    std::thread m_thread;
    //运行标志
    bool m_runningFlag = true;
    //堆栈资源
    struct TStruct
    { int a; int b; } *m_pResource;
private:
    Ui::MianWndClass ui;
};

#mainWnd.cpp

//辅助函数 //时刻值ms 
double DalOsTimeSysGetTime(void)
{
    LARGE_INTEGER nFreq; LARGE_INTEGER nBeginTime;

    QueryPerformanceFrequency(&nFreq);
    QueryPerformanceCounter(&nBeginTime);

    return (double)(nBeginTime.QuadPart * 1000 / (double)nFreq.QuadPart);
}

//辅助函数
void TraceForVStudio(char *fmt, ...)
{
    char out[1024] = { 0 };
    va_list body;
    va_start(body, fmt);
    vsprintf_s(out, 1024, fmt, body);
    va_end(body);    
    OutputDebugStringA(out); 
    OutputDebugStringA("\r\n");
}
//入口函数
void MianWnd::pause_thread(int n)
{
    while (m_runningFlag) //
    {
        if (NULL != m_pResource) //子线程内使用(共享)资源
            TraceForVStudio("Using Resource# a:%d b:%d ", ++m_pResource->a, ++m_pResource->b);

        std::this_thread::sleep_for(std::chrono::seconds(n));
    }
    //may do something other..
}
//停止函数
void MianWnd::MyThreadStop()
{
    m_runningFlag = false;
    m_thread.join();  //block
}
//构造函数
MianWnd::MianWnd(QWidget *parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);
	//资源
    m_pResource = new TStruct();
    //线程
    m_thread = std::thread(&MianWnd::pause_thread, this, 5);
}
//析构函数
MianWnd::~MianWnd()
{
    //停止线程
    TraceForVStudio("Wait Begin At:%f", DalOsTimeSysGetTime()) ;
    MyThreadStop();
    TraceForVStudio("Wait Finish At:%f", DalOsTimeSysGetTime()); 
    //销毁线程资源
    if (NULL != m_pResource)
    { delete m_pResource; m_pResource = nullptr; }
    //销毁UI及其子窗口对象..
}

//关闭窗口触发析构过程
//Using Resource# a:1 b:1
//Using Resource# a:2 b:2
//...
//Wait Begin  At : 93813551.843300
//Exit Thread At : 93816981.917300 //about 3.5s
//Wait Finish At : 93816988.057200 //about 007ms

通过上述测试,可以确定join函数可以起到很好的等待线程退出的效果,比std::condition_variable 方便的多。上述,每5s完成单次循环,我随机关闭窗口触发析构过程,m_runningFlag 置零后大约过了3.5s后生效,然后入口函数退出,又过了7ms左右,join函数从阻塞过程中返回,析构过程继续执行堆栈资源的销毁过程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/32255.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python、pyqt5实现人脸检测、性别和年龄预测

摘要&#xff1a;这篇博文介绍基于opencv&#xff1a;DNN模块自带的残差网络的人脸、性别、年龄识别系统&#xff0c;系统程序由OpenCv, PyQt5的库实现。如图系统可通过摄像头获取实时画面并识别其中的人脸表情&#xff0c;也可以通过读取图片识别&#xff0c;本文提供完整的程…

【IIS建站教程】windows本地搭建web服务,内网穿透发布公网访问

✨个人主页&#xff1a;bit me&#x1f447; 目 录 &#x1f43e;1.前言&#x1f490;2.Windows网页设置&#x1f338;2.1 Windows IIS功能设置&#x1f337;2.2 IIS网页访问测试 &#x1f340;3. Cpolar内网穿透&#x1f339;3.1 下载安装Cpolar&#x1f33b;3.2 Cpolar云端设…

【Leetcode60天带刷】day36——56. 合并区间,738.单调递增的数字

​ 题目&#xff1a; 56. 合并区间 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 示例 1&#xff1a;…

菜鸡shader:L4三色环境光原理妙用并在ue4中实现

三色环境光的拓展运用 我的上一篇博客写了关于三色环境光的原理&#xff0c;这次就来简单拓展一下。最重要的核心思想其实就是取法线向量的第二个分量&#xff0c;因为它控制方法是指向xz平面的上或者下。 所以这次要用这个原来来单独摘出上层环境光&#xff0c;乘上菲涅尔&a…

ASP.NET Core Web API之Token验证

在实际开发中&#xff0c;我们经常需要对外提供接口以便客户获取数据&#xff0c;由于数据属于私密信息&#xff0c;并不能随意供其他人访问&#xff0c;所以就需要验证客户身份。那么如何才能验证客户的身份呢&#xff1f;今天以一个简单的小例子&#xff0c;简述ASP.NET Core…

交叉熵、Focal Loss以及其Pytorch实现

交叉熵、Focal Loss以及其Pytorch实现 本文参考链接&#xff1a;https://towardsdatascience.com/focal-loss-a-better-alternative-for-cross-entropy-1d073d92d075 文章目录 交叉熵、Focal Loss以及其Pytorch实现一、交叉熵二、Focal loss三、Pytorch1.[交叉熵](https://pyto…

Python 动态生成系统数据库设计到word文档

背景 经常需要交付一些系统文档而且基本都是word的&#xff0c;其中又有系统数据库介绍模块&#xff0c; 看着数据库里的几百张表于是我开始怀疑人生, 所以咱手写一个 涉及知识 pymysql 操作数据库 -tkinter GUI图形库threading 线程queue 阻塞队列pandas python数据计算…

layui(5)——内置模块分页模块

模块加载名称&#xff1a;laypage laypage 的使用非常简单&#xff0c;指向一个用于存放分页的容器&#xff0c;通过服务端得到一些初始值&#xff0c;即可完成分页渲染&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset&quo…

RocketMQ --- 实战篇

一、案例介绍 1.1、业务分析 模拟电商网站购物场景中的【下单】和【支付】业务 1.1.1、下单 流程 用户请求订单系统下单 订单系统通过RPC调用订单服务下单 订单服务调用优惠券服务&#xff0c;扣减优惠券 订单服务调用调用库存服务&#xff0c;校验并扣减库存 订单服务调…

长尾关键词有什么作用?要怎么用?

长尾关键词很多的网站都会忽略其存在&#xff0c;其实你不要小看长尾关键词&#xff0c;他将带给网站的流量也是极其可观的&#xff0c;所说比不上那些重点关键词的流量&#xff0c;但是对提升网站的权重还是有着重要的作用。 长尾关键词有什么用&#xff1f;长尾关键词的3…

Gitlab群组及项目仓库搭建

1、新建群组 2、新建项目 3、克隆到Visualstudio 复制克隆地址&#xff0c;克隆到本地 这里会让你登录账号 可以添加成员并邀请ta进项目组 从已注册用户列表中选择 4、Git工作流 回顾一下Git工作流&#xff0c;工程人员只需要从Develop分支新建自己的分支即可。分支命名以姓名…

CadLib 4.0.2023.31601 net for Windows Crack

CadLib 4.0 for Windows&#xff1a;在 C# VB .NET 中读取、写入和显示 AutoCAD DWG 和 DXF 文件 CadLib 4.0 for Windows仅在Windows上运行&#xff0c;并且基于.NET 4.x。 CadLib 4.0读取、写入和显示 C#、VB.NET 或任何其他 .NET 语言的 AutoCAD™ DWG 和 DXF 文件。下载试…

2-css-3

一 选择器 1 结构伪类选择器 作用&#xff1a;根据元素的结构关系查找元素。 选择器说明E:first-child查找第一个E元素E:last-child查找最后一个E元素E:nth-child(N)查找第N个E元素&#xff08;第一个元素N值为1&#xff09; li:first-child {background-color: green; }2 :…

5.6.3 套接字

5.6.3 套接字 我们先以示例引入套接字的基本内容&#xff0c;我们知道在邮政通信的时候我们需要在信封上写明我们的收件地址&#xff0c;比如北京市海淀区双清路30号清华大学8444号某某某收&#xff0c;这其中我们需要一个物理地址“北京市海淀区双清路30号”&#xff0c;一个…

6.22 驱动开发作业

字符设备驱动内部实现原理 1.字面理解解析&#xff1a; 字符设备驱动的内部实现有两种情况&#xff1a; 情况1.应用层调用open函数的内部实现&#xff1a; open函数的第一个参数是要打开的文件的路径&#xff0c;根据这个路径 虚拟文件系统层VFS 可以找到这个文件在文件系统…

openeuler22.03系统salt-minion启动报“Invalid version: ‘cpython‘“错的问题处理

某日&#xff0c;检查发现一台openeuler22.03 SP1系统的服务器上之前正常运行的saltstack客户端minion未运行&#xff0c;查看服务状态&#xff0c;报"Invalid version: cpython"错&#xff0c;无法正常运行&#xff0c;本文记录问题处理过程。 一、检查salt-minion…

uniapp中小程序的生命周期

一、uni-app应用生命周期 函数名说明onLuaunch当uni-app 初始化完成时触发&#xff08;全局只触发一次&#xff09;onShow当 uni-app 启动&#xff0c;或从后台进入前台显示onHide当 uni-app 从前台进入后台onError当 uni-app 报错时触发onUniNViewMessage对 nvue 页面发送的数…

android jetpack Room的基本使用(java)

数据库的基本使用 添加依赖 //roomdef room_version "2.5.0"implementation "androidx.room:room-runtime:$room_version"annotationProcessor "androidx.room:room-compiler:$room_version"创建表 Entity表示根据实体类创建数据表&#xff0c…

发送图文并茂的html格式的邮件

本文介绍如何生成和发送包含图表和表格的邮件&#xff0c;涉及echarts图表转换为图片、图片内嵌到html邮件内容中、html邮件内容生成、邮件发送方法等 一、图表处理 因为html格式的邮件不支持echarts,也不支持js执行&#xff0c;所以图表需要转换为图片内嵌在邮件内容中 因为平…

【Java】Java核心 73:XML (中)

文章目录 5 XML的组成&#xff1a;字符区(了解)**6** **DTD约束(能够看懂即可)****1** **什么是DTD****2** **DTD约束的实现和语法规则&#xff08;看懂dtd约束&#xff0c;书写符合规范的xml文件&#xff09;** 5 XML的组成&#xff1a;字符区(了解) 当大量的转义字符出现在x…