In fact, there is no language or operating system that gives you the convenience of suddenly terminating threads asynchronously without warning you not to use them.

This article is shared from huawei cloud community “how to write efficient, elegant, trusted code series (1) — C++ multi-thread forced termination”, original author: I am a big watermelon.

The origin of the story comes from the fact that when I was optimizing others’ c++ source code, I wanted to improve the operation efficiency of the program through multi-threading. There were mainly the following requirements and difficulties:

1. Multiple threads run the model in parallel to see which model runs faster. After running out, other threads are terminated

2. The source code model is very complex, many function calls, not easy to change, so it is not suitable for the termination of communication through signals or signs

Search the Internet for several ways to terminate a thread:

1. Return of thread function (recommended). This is the safest way to exit the thread. After the thread returns, it will clean up the class objects requested by the function, that is, call the destructor of these objects. The _endthreadex() function is then automatically called to clean up the resources (mainly tidData objects created) requested 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 undo any thread, with the hThread parameter used to identify the handle of the terminated thread. When a thread terminates running, its exit code becomes the value you pass as the dwExitCode argument. At the same time, the thread’s kernel object usage count is 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 there is no guarantee that the thread will be canceled when the function returns. If you want to know for sure that the thread has terminated, you must call WaitForSingleObject or a similar function, passing in a handle to the thread.

3. By calling the ExitThread function, the thread will undo itself (preferably without this method). This function terminates the thread and causes the operating system to clean up all operating system resources used by the thread. However, C++ resources, such as C++ class objects, will not be destructed.

4. The ExitProcess and TerminateProcess functions can also be used to terminate a thread (this method should be avoided).

Options 2 and 3 can cause memory leaks, and 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 strongly recommend, or even require, that developers build multithreaded applications on the basis of cooperative or synchronous thread termination.

Existing thread termination functions, including pthread_exit() and pthread_cancel() in Pthread.h on Linux, The ExitThread() and TerminateThread() function in Win32.h does not provide the ability to kill a thread, but to passively wait for its natural end. The destructor can only terminate the joinable thread when it is still. For joinable/detached threads, the destructor cannot terminate the thread at all.

To terminate the threading of OS/compiler-related functions, we need to know how to get the native thread data type STD ::thread from C++. Fortunately, STD :: Thread provides an API native_handle() to get the thread’s native handle type before or after the call. This local handle can also be passed to a local OS thread termination function, such as join()detach() pthread_cancel().

The following code is used to display STD ::thread::native_handle(), STD ::thread::get_id() and pthread_self() returns 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();
}
Copy the code

You can run it and get the result

$ 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
Copy the code

After uncommentpos 1 or pos2, that is, calling join() or after detach(), the C++ thread loses information about 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
Copy the code

Therefore, to effectively call native thread termination functions (such as pthread_cancel), you need to save the native handle STD :: Thread ::detach() on or before calling STD :: Thread :: Join (). This way, the thread can always be terminated with a valid native handle.

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; }Copy the code

As a result,

$ 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:
Copy the code

Of course, if possible, it is best to terminate the thread by means of return or signal, which also meets the requirements of safety and trust.

Click to follow, the first time to learn about Huawei cloud fresh technology ~