C++11 中引入了 std::thread 可以比较方便的创建和管理多线程,这篇笔记主要简单记录了一下我的学习过程。包括线程的创建的管理还有在类中相关的用法。
requirement
为了使用 std::thread
我们需要添加 <thread>
作为头文件,同时如果使用 cmake
进行项目编译管理的话,需要添加以下两行进行相关库的链接,然后就可以使用:
find_package (Threads)
...
add your executable
...
target_link_libraries (your_project_name ${CMAKE_THREAD_LIBS_INIT})
basic usage
std::thread
的使用比较简单,直接通过构造函数可以创建一个线程,有需要的话也可以传入参数,见以下代码:
// 无参数函数
void foo () {
std::cout << "A thread function!" << std::endl;
}
// 有参数函数
void fooWithName (std::string func_name) {
std::cout << "A thread function with name: " << func_name << "!" << std::endl;
}
int main() {
std::thread t1(foo);
t1.join();
std::thread t2(fooWithName, "FuncName");
t2.join();
return 0;
}}
输出:
A thread function!
A thread function with name: FuncName!!
join() vs detach()
可以看到,上面代码例子中创建的两个线程后都增加了一条 join()
方法,这是因为通过 std::thread
创建的线程默认下并不是独立于主线程 (main()
)的,如果不加 join()
的话,当主线程退出之后子线程也会报错并退出,见如下代码:
void fooWithTime(int n_seconds, std::string name) {
for (int i = 0; i < n_seconds; i++) {
usleep(1000);
std::cout << name <<" Thread: worked for " << i + 1 << " seconds.." << std::endl;
}
}
int main() {
std::thread t1(fooWithTime, 5, "Child");
fooWithTime(3, "Main");
t1.join();
std::cout << "Main thread exit! " << std::endl;
return 0;
}}
由于在 main
函数最后加入 t1.join()
所以在主函数输出 3 秒之后会等到 子线程工作完返回时才退出,输出如下:
Main Thread: worked for 1 seconds..
Child Thread: worked for 1 seconds..
Main Thread: worked for 2 seconds..
Child Thread: worked for 2 seconds..
Main Thread: worked for 3 seconds..
Child Thread: worked for 3 seconds..
Child Thread: worked for 4 seconds..
Child Thread: worked for 5 seconds..
Main thread exit!
如果把 t1.join()
去掉之后,输出如下:
Main Thread: worked for 1 seconds..
Child Thread: worked for 1 seconds..
Child Thread: worked for 2 seconds..
Main Thread: worked for 2 seconds..
Child Thread: worked for 3 seconds..
Main Thread: worked for 3 seconds..
Main thread exit!
terminate called without an active exceptionn
可以看到在主线程退出之后,子线程也被强制退出了,如果想让子线程独立于主线程的话,需要加入detach()
代码和输出如下:
void fooWithTime(int n_seconds, std::string name) {
for (int i = 0; i < n_seconds; i++) {
usleep(1000);
std::cout << name <<" Thread: worked for " << i + 1 << " seconds.." << std::endl;
}
}
int main() {
std::thread t1(fooWithTime, 5, "Child");
t1.detach();
fooWithTime(3, "Main");
std::cout << "Main thread exit! " << std::endl;
return 0;
}
Main Thread: worked for 1 seconds..
Child Thread: worked for 1 seconds..
Main Thread: worked for 2 seconds..
Child Thread: worked for 2 seconds..
Main Thread: worked for 3 seconds..
Main thread exit!
可以发现,我们虽然 detach 了子线程,但是从输出上来看在主线程退出之后子线程也没有输出了,这是因为在子线程 detach 了之后,主线程退出的同时主进程也同时退出了,而我们运行进程时只能看到该进程的输出,所以就看不到 detach
后的线程的输出了。值得注意的是一旦线程被detach 之后就不能再进行 join
操作了,所以对 detach
的使用需要谨慎一点,并且在对一个线程进行 join
之前,应该通过 joinable()
进行判断。如下所示:
void fooWithTime(int n_seconds, std::string name) {
for (int i = 0; i < n_seconds; i++) {
usleep(1000);
std::cout << name <<" Thread: worked for " << i + 1 << " seconds.." << std::endl;
}
}
int main() {
std::thread t1(fooWithTime, 5, "Child");
t1.detach();
if (t1.joinable()) {
t1.join();
} else {
std::cout << "Thread unjoinable!" << std::endl;
}
fooWithTime(3, "Main");
std::cout << "Main thread exit! " << std::endl;
return 0;
}
threads with class
在实际使用中,我们的项目通过使用了各种类,下面代码演示了如何把类和 std::thread
结合使用:
class A {
public:
std::string class_param;
void funcWithoutParam() {
std::cout << "non-static func: class param: " << class_param << std::endl;
}
void funcWithParam(std::string external_param) {
std::cout << "non-static func: class param: " << class_param << ", external param: " << external_param << std::endl;
}
static void static_func(std::string param) {
std::cout << "static func: param: " << param << std::endl;
}
};
int main() {
A a;
a.class_param = "Apple";
std::thread t1(&A::funcWithoutParam, a);
t1.join();
std::thread t2(&A::funcWithParam, a, "banana");
t2.join();
std::thread t3(&A::static_func, "Cherry");
t3.join();
std::cout << "Main thread exit! " << std::endl;
return 0;
}
输出:
non-static func: class param: Apple
non-static func: class param: Apple, external param: banana
static func: param: Cherry
Main thread exit!
上面包含了三种用法:
- 如果传入线程的函数是类中的
static
函数,可以直接传入函数指针和相关的参数,主要这里的函数比如显示地用&
进行传入; - 如果传入线程的函数是
non-static
函数,我们还必须要传入该类的某个实例,并且这样传入的线程可以调用该实例所有变量,构造函数顺序是类函数,实例,参数1, 参数2, ...
conclusion
这篇笔记简单概括了一下 std::thread
,值得注意的是,上面的所有例子都直接最简单的应用,并没有涉及资源分配的情况,在实际使用需要注意不同线程的对同一资源的使用 (race_condition),避免出现死锁。