#include <iostream>
#include <thread>
void function_1() {
std::cout << "I'm function_1()" << std::endl;
}
int main() {
std::thread t1(function_1);
// do other things
t1.join();
return 0;
}
创建一个std::thread 对象 t1, 构造时传递一个callable参数, 作为这个线程的入口,线程对象创建成功后,线程也就成功启动了,
直到入口函数执行完毕,线程也就执行完成了。
当执行例程返回后,线程就会终止,这时候需要回收线程的资源。有两种方式指定回收的方式:
- join: 调用线程等待线程对象关联的执行线程执行结束,然后回收它的资源。
- detach:调用线程不希望等待,因此将线程对象和它关联的执行线程分离,交由 C++ Runtime管理该执行线程。
一个被分离的线程也成为守护线程(daemon thread),概念与Linux的守护线程相似,这类线程通常都长期运行于后台。
一个线程对象要么被join, 要么被detach。 如果两者都无, 那么线程对象在析构时会调用 std::terminate, 使得程序异常结束。可以通过调用thread::joinable判断线程是否可以被join或 detach。
上面代码使用了 t1.join(), 主线程会一直阻塞着,直到子线程完成,join()函数的另一个任务是回收该线程中的资源。 但是我们通常不想这么做,因为我们使用多线程,是为了提高并发度。如果创建一个线程之后就把另一个线程投入等待,那么程序的并发度就得不到提高。
线程对象和对象内部管理的生命周期并不一样,如果线程执行得快,可能内部的线程已经结束了,但是线程对象还活着,也有可能线程对象已经被析构了,但是内部的线程还在运行。
假设t1线程是一个执行很慢的进程,主线程并不想等子线程结束就想结束整个任务,这时我们可以选择调用t1.detach(), 从而将t1线程放在后台运行,所有权和控制权交给C++运行时库。这样,线程被分离之后,即使该线程对象被析构了,线程还是能够在后台运行,只是由于对象被析构了,主线程不能通过对象名与这个线程进行通信。 例如:
#include <iostream>
#include <thread>
void function_1() {
//延时500ms 为了保证test()运行结束之后才打印
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << "I'm function_1()" << std::endl;
}
void test() {
std::thread t1(function_1);
t1.detach();
// t1.join();
std::cout << "test() finished" << std::endl;
}
int main() {
test();
//让主线程晚于子线程结束
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); //延时1s
return 0;
}
// 使用 t1.detach()时
// test() finished
// I'm function_1()
// 使用 t1.join()时
// I'm function_1()
// test() finished
由于线程入口函数内部有个500ms的延时,所以在还没有打印的时候,test()就已经执行完成了,t1对象已经被析构了,但是它负责的那个线程还是能够运行。
如果去掉main中1s的延时,会发现
I'm function_1()
没有输出, 这是因为主线程执行太快,整个程序已经结束了,那个后台线程被C++运行时库回收了。
如果将t1.detach()换成t1.join(), test函数会在t1线程执行结束之后,才执行结束。
一旦一个线程被分离了,就不能再被join了。