In C++11, C++ introduced built-in support for threads std::thread
. Since then, starting a new thread in C++ is as easy as defining an object. However, dynamically terminating a running C++ thread is still very tricky, especially for the joined/detached thread. There are plenty of discussions of this topic, and the conclusion is that
“terminate 1 thread + forcefully (target thread doesn’t cooperate) + pure C++11 = No way”.
This post is not aimed to provide a magic to kill C++ thread with built-in C++ APIs - apologize for it. Instead, I am going to explain how to kill a C++ thread using native(OS/compiler-dependent) function.
The complete code in this post can be found on my github, which is built with GCC 4.8.5 and verified on CentOS 7.
1. std::thread
destructor not work
The destructor std::thread::~thread()
can only terminate the thread when the thread is still joinable
. A C++ thread is joinable
after it is started and before calling join
or detach
(see this example). So for joined/detached thread, the std::thread
destructor cannot terminate the thread at all.
Following is an class to dynamically start and stop a thread. The std::unordered_map<std::string, std::thread>
is used to record thread name and thread. And in stop_thread()
, the std::thread
destructor is directly called.
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);
thrd.detach();
tm_[tname] = std::move(thrd);
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()) {
it->second.std::thread::~thread(); // thread not killed
tm_.erase(tname);
std::cout << "Thread " << tname << " killed:" << std::endl;
}
}
private:
typedef std::unordered_map<std::string, std::thread> ThreadMap;
ThreadMap tm_;
};
To show the running threads, function show_thread()
is called everytime starting/stopping a thread.
void show_thread(const std::string &keyword)
{
std::string cmd("ps -T | grep ");
cmd += keyword;
system(cmd.c_str());
}
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);
show_thread(keyword);
foo.stop_thread(tname1);
show_thread(keyword);
// create and kill thread 2
foo.start_thread(tname2);
show_thread(keyword);
foo.stop_thread(tname2);
show_thread(keyword);
return 0;
}
The output of the the above test code is like:
$ g++ -Wall -std=c++11 kill_cpp_thread.cc -o kill_cpp_thread -pthread -lpthread
$ ./kill_cpp_thread
Thread test_thread1 created:
29469 29470 pts/5 00:00:00 test_thread1
Thread test_thread1 killed:
29469 29470 pts/5 00:00:00 test_thread1
Thread test_thread2 created:
29469 29470 pts/5 00:00:00 test_thread1
29469 29477 pts/5 00:00:00 test_thread2
Thread test_thread2 killed:
29469 29470 pts/5 00:00:00 test_thread1
29469 29477 pts/5 00:00:00 test_thread2
Obviously, the test threads could not be killed in Foo::stop_thread()
.
2. std::thread::id
vs. pthread_t
To terminate threads with OS/compiler-dependent functions, we need to know how to get the native thread data type from C++ std::thread
. Fortunately, std::thread
provides an API native_handle()
to get the thread’s native handle type - before calling join()
or detach()
. And this native handle can be passed to native OS thread terminate function, e.g. pthread_cancel()
.
Here’s a demo code to show the fact that the std::thread::native_handle()
, std::thread::get_id()
and pthread_self()
return the same pthread_t
to handle a C++ thread for Linux/GCC. You also can try it out online.
#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();
//t1.detach(); t2.detach();
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();
}
Build and run it get the following 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
However, after calling join()
or detach()
, the C++ thread loses the info of native handle type:
int main()
{
std::thread t1(f, 1), t2(f, 2);
t1.join(); t2.join();
//t1.detach(); t2.detach();
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;
}
$ ./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
So if you call pthread_cancel(t1.native_handle())
after t1
joined/detached, your program will definitely coredump.
3. The Terminator
So in summary, to effectively call native thread termination function(e.g. pthread_cancel
), you need to save the native handle before calling std::thread::join()
or std::thread::detach()
. So that your native terminator always has a valid native handle to use.
Following is the revised Foo
class that can stop C++ 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_;
};
And the result is:
$ 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: