摘要:实际上,没有任何语言或操作系统可以为你提供异步突然终止线程的便利,且不会警告你不要使用它们。

本文分享自华为云社区《如何编写高效、优雅、可信代码系列(1)——C++多线程强制终止》,原文作者:我是一颗大西瓜  。

故事的起因来源于我在优化他人c++源码的时候,想通过多线程的方式提升程序的运算效率,主要存在以下需求和难点:

  1. 多个线程并行跑模型,看哪个模型跑的快,跑出来后结束其他线程,线程间独立运行无通信过程
  2. 源码模型很复杂,函数调用较多,不好改动,因此不太适合通过信号或标志进行通信终止

网上搜索了一下线程结束的几种方式:

  1. 线程函数的return返回(建议)。这种退出线程的方式是最安全的,在线程函数return返回后, 会清理函数内申请的类对象, 即调用这些对象的析构函数.。然后会自动调用 _endthreadex()函数来清理 _beginthreadex()函数申请的资源(主要是创建的tiddata对象)。
  2. 同一个进程或另一个进程中的线程调用TerminateThread函数(应避免使用该方法)。TerminateThread能够撤消任何线程,其中hThread参数用于标识被终止运行的线程的句柄。当线程终止运行时,它的退出代码成为你作为dwExitCode参数传递的值。同时,线程的内核对象的使用计数也被递减。注意TerminateThread函数是异步运行的函数,也就是说,它告诉系统你想要线程终止运行,但是,当函数返回时,不能保证线程被撤消。如果需要确切地知道该线程已经终止运行,必须调用WaitForSingleObject或者类似的函数,传递线程的句柄。
  3. 通过调用ExitThread函数,线程将自行撤消(最好不使用该方法)。该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是,C++资源(如C++类对象)将不被析构。
  4. ExitProcess和TerminateProcess函数也可以用来终止线程的运行(应避免使用该方法)。

选项2和3可能会导致内存泄漏,实际上,没有任何语言或操作系统可以为你提供异步突然终止线程的便利,且不会警告你不要使用它们。所有这些执行环境都强烈建议开发人员,甚至要求在协作或同步线程终止的基础上构建多线程应用程序。

现有的线程结束函数,包括linux系统的pthread.h中的pthread_exit()和pthread_cancel(),windows系统的win32.h中的ExitThread()和TerminateThread(),也就是说,C++没有提供kill掉某个线程的能力,只能被动地等待某个线程的自然结束,析构函数~thread()也不能停止线程,析构函数只能在线程静止时终止线程joinable,对于连接/分离的线程,析构函数根本无法终止线程。

要终止与OS /编译器相关的函数的线程,我们需要知道如何从C++获取本机线程数据类型std::thread。幸运的是,在调用或之前std::thread提供了一个API native_handle()以获取线程的本机句柄类型。并且可以将此本地句柄传递给本地OS线程终止函数,例如join() detach() pthread_cancel()。

以下代码用于显示std::thread::native_handle(),std::thread::get_id()并pthread_self()返回相同的代码pthread_t来处理Linux / GCC的C++线程

#include <mutex>

#include <iostream>

#include <chrono>

#include <cstring>

#include <pthread.h>

std::mutex iomutex;

void f(int num)

{

    std::this_thread::sleep_for(std::chrono::seconds(1));

    std::lock_guard<std::mutex> lk(iomutex);

    std::cout << "Thread " << num << " pthread_t " << pthread_self() << std::endl;

}

int main()

{

    std::thread t1(f, 1), t2(f, 2);

    //t1.join(); t2.join();  ----------------pos 1

    //t1.detach(); t2.detach(); -------------pos 2

    std::cout << "Thread 1 thread id " << t1.get_id() << std::endl;

    std::cout << "Thread 2 thread id " << t2.get_id() << std::endl;

    std::cout << "Thread 1 native handle " << t1.native_handle() << std::endl;

    std::cout << "Thread 2 native handle " << t2.native_handle() << std::endl;

    t1.join(); t2.join();

    //t1.detach(); t2.detach();

}

运行后可以得到结果

$ g++ -Wall -std=c++11 cpp_thread_pthread.cc -o cpp_thread_pthread -pthread -lpthread

$ ./cpp_thread_pthread

Thread 1 thread id 140109390030592

Thread 2 thread id 140109381637888

Thread 1 native handle 140109390030592

Thread 2 native handle 140109381637888

Thread 1 pthread_t 140109390030592

Thread 2 pthread_t 140109381637888

uncommentpos 1或者pos 2后,即调用join()或之后detach(),C++线程会丢失本机句柄类型的信息

$ ./cpp_thread_pthread

Thread 1 pthread_t 139811504355072

Thread 2 pthread_t 139811495962368

Thread 1 thread id thread::id of a non-executing thread

Thread 2 thread id thread::id of a non-executing thread

Thread 1 native handle 0

Thread 2 native handle 0

因此,要有效地调用本机线程终止函数(例如pthread_cancel),需要在调用std::thread::join()时或之前保存本机句柄std::thread::detach()。这样,始终可以使用有效的本机句柄终止线程。

class Foo {

public:

    void sleep_for(const std::string &tname, int num)

    {

        prctl(PR_SET_NAME,tname.c_str(),0,0,0);       

        sleep(num);

    }

    void start_thread(const std::string &tname)

    {

        std::thread thrd = std::thread(&Foo::sleep_for, this, tname, 3600);

        tm_[tname] = thrd.native_handle();

        thrd.detach();

        std::cout << "Thread " << tname << " created:" << std::endl;

    }

    void stop_thread(const std::string &tname)

    {

        ThreadMap::const_iterator it = tm_.find(tname);

        if (it != tm_.end()) {

            pthread_cancel(it->second);

            tm_.erase(tname);

            std::cout << "Thread " << tname << " killed:" << std::endl;

        }

    }

private:

    typedef std::unordered_map<std::string, pthread_t> ThreadMap;

    ThreadMap tm_;

};

int main()

{

    Foo foo;

    std::string keyword("test_thread");

    std::string tname1 = keyword + "1";

    std::string tname2 = keyword + "2";

    // create and kill thread 1

    foo.start_thread(tname1);

    foo.stop_thread(tname1);

    // create and kill thread 2

    foo.start_thread(tname2);

    foo.stop_thread(tname2);

    return 0;

}

结果是

$ g++ -Wall -std=c++11 kill_cpp_thread.cc -o kill_cpp_thread -pthread -lpthread

$ ./kill_cpp_thread

Thread test_thread1 created:

30332 30333 pts/5    00:00:00 test_thread1

Thread test_thread1 killed:

Thread test_thread2 created:

30332 30340 pts/5    00:00:00 test_thread2

Thread test_thread2 killed:

当然,条件允许的话最好还是使用返回或信号的方式终止线程,这样也符合安全可信的要求。

点击关注,第一时间了解华为云新鲜技术~

C++多线程强制终止的更多相关文章

  1. 如何按名称或PID查找一个进程?如何按端口号查找一个进程?如何查看一个进程的CPU和内存、文件句柄使用情况?如何查看CPU利用率高的TOP10进程清单?如何根据PID强制终止进程?

    如何按名称或PID查找一个进程?如何按端口号查找一个进程?如何查看一个进程的CPU和内存.文件句柄使用情况?如何查看CPU利用率高的TOP10进程清单? 目录 如何按名称或PID查找一个进程?如何按端 ...

  2. Eclispe造成的tomcat占用端口 无法启动 强制终止进程 转载

    很多时候运行tomcat 的时候总是会提示tomcat 的端口被占用 但是任务管理器里面还找不到是哪个端口被占用了 因此很多人就重新配置tomcat  或者去修改tomcat的端口号 ,其实这么做太麻 ...

  3. Python中的多进程与多线程(一)

    一.背景 最近在Azkaban的测试工作中,需要在测试环境下模拟线上的调度场景进行稳定性测试.故而重操python旧业,通过python编写脚本来构造类似线上的调度场景.在脚本编写过程中,碰到这样一个 ...

  4. 第一章 Java多线程技能

    1.初步了解"进程"."线程"."多线程" 说到多线程,大多都会联系到"进程"和"线程".那么这两者 ...

  5. C#多线程学习笔记

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threa ...

  6. python 多线程学习

    多线程(multithreaded,MT),是指从软件或者硬件上实现多个线程并发执行的技术 什么是进程? 计算机程序只不过是磁盘中可执行的二进制(或其他类型)的数据.它们只有在被读取到内存中,被操作系 ...

  7. Delphi中线程类TThread实现多线程编程1---构造、析构……

    参考:http://www.cnblogs.com/rogee/archive/2010/09/20/1832053.html Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大 ...

  8. C#中的多线程 - 基础知识

    原文:http://www.albahari.com/threading/ 文章来源:http://blog.gkarch.com/threading/part1.html 1简介及概念 C# 支持通 ...

  9. Java多线程程序设计详细解析

    一.理解多线程 多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立. 线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线 ...

  10. Python3 多进程和多线程

    Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊.普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为 ...

随机推荐

  1. 如何优雅重启 kubernetes 的 Pod

    最近在升级服务网格 Istio,升级后有个必要的流程就是需要重启数据面的所有的 Pod,也就是业务的 Pod,这样才能将这些 Pod 的 sidecar 更新为新版本. 方案 1 因为我们不同环境的 ...

  2. go mod tidy总是安装最新依赖,如何查找哪个模块导致某个包安装最新依赖,提供一个小工具

    安装: go install github.com/jan-bar/interesting/findModVer@latest 执行:findModVer d:\myproject 结果如下图所示: ...

  3. List集合异常:list All elements are null

    查询数据库,返回空数据,但是List中显示有一个数据,点开以后显示  All elements are null ObjectUtils.isEmpty 和 List.isEmpty判断都失败,后续会 ...

  4. OpenGL 摄像机视角详解

    1. 摄像机 摄像机就好像是我们的眼睛,我们从摄像机的方向观察世界空间中的模型.摄像机远离模型,模型自然就变小了(透视投影下),然而,在GL中事实上并没有摄像机的概念.但是我们可以通过移动世界空间远离 ...

  5. STM32F103 CAN通信波特率的计算方法

    前言 以下的波特率计算和实例仅针对 STM32F1系列 最近看一下CAN通信,翻出来之前做过的STM32 CAN通信的项目代码,有些概念比较模糊了,如波特率是怎么计算的. 最近接触rt-thread比 ...

  6. 文心一言 VS 讯飞星火 VS chatgpt (134)-- 算法导论11.2 6题

    六.用go语言,假设将n 个关键字存储到一个大小为 m 且通过链接法解决冲突的散列表中,同时已知每条链的长度,包括其中最长链的长度 L,请描述从散列表的所有关键字中均匀随机地选择某一元素并在 O(L· ...

  7. Aspire 框架预览版发布,使云原生开发和运维更加简单

    随着 .NET 8 的发布,.NET Aspire 也随之发布,这是一个全家桶框架旨在加快基于云的应用程序的构建..NET Aspire 从一开始就集成了关键组件,例如遥测和运行状况检查.它还承诺提供 ...

  8. Android 线性布局和相对布局

    LinearLayout 线性布局 orientation 排列方式:vertical 竖直 horizontal 水平(默认值) layout_weight 权重:将子视图以对应的权重分配其空间大小 ...

  9. iOS程序生命周期流程

    盛年不重来,一日难再晨.及时宜自勉,岁月不待人.   iOS系统资源是有限的,合理的去配置应用程序的运行状态以及了解app的生命周期对于大多数开发者来说是很重要的一件事情!在后台进行运行时app会受到 ...

  10. 【生活技巧记录】歌词Lyric生成及音乐标签嵌入

    前置工具准备: BesLyric:一款专门制作 网易云音乐 LRC 滚动歌词的软件! 搜索.下载.制作 歌词更方便! Foobar 2000:一款适用于 Windows 平台的高级免费软件音频播放器 ...