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

本文分享自华为云社区《如何编写高效、优雅、可信代码系列(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. JDK 动态代理原理

    代理模式 客户端不直接访问目标对象,需要通过第三者来实现间接访问对象 代理对象在客户端和目标对象之间起中介作用,能够屏蔽目标对象不想让客户端知道的内容,或增加额外的服务 动态代理 JDK 动态代理:基 ...

  2. 微软发布开源平台 Radius:高效构建、运行基于Dapr 云原生应用程序

    Microsoft Azure 孵化团队很高兴地宣布[1]推出一个名为 Radius 的新开放应用程序平台,该平台将应用程序置于每个开发阶段的中心,重新定义应用程序的构建.管理和理解方式.Radius ...

  3. 记一次有趣的 buffer overflow detected 问题分析

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 环境说明   无 前言   在我开发的一个实验和学习库中,在很久 ...

  4. 多维评测指标解读第17届MSU世界编码器大赛全高清10bit赛道结果

    超高清视频纤毫毕现的关键一环. 01 主要指标多项第一,带宽节省48% 近日,第17届MSU世界编码器大赛全高清10bit赛道成绩揭晓,阿里自研的H.266/VVC编码器Ali266在该赛道最高效的1 ...

  5. PTA乙级1038C++哈希解法

    #include"bits/stdc++.h" using namespace std; int main() { int a,b[105]={0}; long i,n,K; ci ...

  6. Java SPI机制学习之开发实例

    原创/朱季谦 在该文章正式开始前,先对 Java SPI是什么做一个简单的介绍. SPI,是Service Provider Interface的缩写,即服务提供者接口,它允许开发人员定义一组接口,并 ...

  7. 请问您今天要来点 ODT 吗

    梗出处:请问您今天要来点兔子吗? 这篇文章主要记录一下自己学习 \(\text{ODT}\) 发生的种种. CF896C Willem, Chtholly and Seniorious \(\text ...

  8. 线性代数导论MIT第二章知识点下

    2.3--2.7的知识点 1.使用矩阵消元 2.消元矩阵 3.行交换矩阵 4.增广矩阵 2.4 矩阵运算规则 行与列 方块矩阵与方块乘法 舒尔补充 2.5逆矩阵 乘积AB的逆矩阵 高斯乔丹消元法计算A ...

  9. Modbus转PROFIBUS DP 通信网关-应用案例

    针对西门子S7系列的PLC,通用串口/PROFIBUS-DP网关(PM-160)为建立西门子PLC与现场RS232/485设备的连接提供了理想解决方案

  10. The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission,iphone手机video标签报错

    The request is not allowed by the user agent or the platform in the current context, possibly becaus ...