Abstract: In fact, no language or operating system can provide you with the convenience of abruptly terminating threads asynchronously, and will not warn you not to use them.

This article is shared from the HUAWEI cloud community "How to write efficient, elegant, and credible code series ", the original author: I am a big watermelon.

The origin of the story comes from when I was optimizing the c++ source code of others, I wanted to improve the computational efficiency of the program through multi-threading. The main requirements and difficulties are as follows:

  1. Multiple threads run the model in parallel, see which model runs fast, and terminate other threads after running out, and there is no communication process between threads running independently
  2. The source code model is very complicated, there are many function calls, and it is not easy to change, so it is not suitable for communication termination through signals or flags

I searched the Internet for several ways to end the thread:

1. The return of the thread function (recommended). is the safest way to exit the thread. After the thread function return returns, the class objects applied in the function will be cleaned up, that is, the destructor of these objects will be called. Then the _endthreadex() function is automatically called to clean up the resources (mainly the created tiddata object) applied by the _beginthreadex() function.
2. A thread in the same process or another process calls the TerminateThread function (this method should be avoided). TerminateThread can cancel any thread, where the hThread parameter is used to identify the handle of the thread that was terminated. When the thread terminates, its exit code becomes the value you pass as the dwExitCode parameter. At the same time, the usage count of the thread's kernel object is also decremented. Note that the TerminateThread function is a function that runs asynchronously, that is, it tells the system that you want the thread to terminate, but when the function returns, there is no guarantee that the thread will be cancelled. If you need to know exactly that the thread has terminated, you must call WaitForSingleObject or a similar function to pass the thread handle.
3. By calling the ExitThread function, the thread will cancel itself (it is best not to use this method). This function will terminate the running of the thread and cause the operating system to clear all operating system resources used by the thread. However, C++ resources (such as C++ class objects) will not be destructed.
4. ExitProcess and TerminateProcess functions can also be used to terminate the running of the thread (should avoid using this method).

Options 2 and 3 may cause memory leaks. In fact, no language or operating system can provide you with the convenience of abruptly terminating threads asynchronously without warning you not to use them. All of these execution environments are strongly recommended for developers, and even require multi-threaded applications to be built on the basis of cooperative or synchronized thread termination.

Existing thread termination functions, including pthread_exit() and pthread_cancel() in pthread.h of linux system, ExitThread() and TerminateThread() in win32.h of windows system, other words, C++ does not provide kill The ability of a thread can only passively wait for the natural end of a thread. The destructor ~thread() cannot stop the thread either. The destructor can only terminate the thread joinable when the thread is stationary. For connected/detached threads , The destructor cannot terminate the thread at all.

To terminate the thread of OS/compiler-related functions, we need to know how to get the native thread data type std::thread from C++. Fortunately, before calling or before std::thread provides an API native_handle() to get the thread's native handle type. And you can pass this local handle to the local OS thread termination function, such as join() detach() pthread_cancel().

The following code is used to show that std::thread::native_handle(), std::thread::get_id() and pthread_self() return the same code pthread_t to handle Linux/GCC C++ threads

#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();

}

You can get the result after running

$ 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

After uncommentpos 1 or pos 2, that is, after calling join() or detach(), the C++ thread will lose the information of the native handle type

$ ./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

Therefore, to effectively call the native thread termination function (such as pthread_cancel), you need to save the native handle std::thread::detach() when or before calling std::thread::join(). In this way, a valid native handle can always be used to terminate the thread.

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;

}

The results are

$ 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:

Of course, if conditions permit, it is better to terminate the thread by means of return or signal, which also meets the requirements of safety and credibility.

Click to follow, and get to know the fresh technology of


华为云开发者联盟
1.4k 声望1.8k 粉丝

生于云,长于云,让开发者成为决定性力量