C++ 线程笔记之一:join和detach

ref

前言

本文讲解关于C++11标准引入的std::thread类的join和detach方法。

join和detach

  • 创建std::thread对象后,子线程会被创建,且该子线程会立即启动,没有显式的start函数启动子线程;
  • std::thread对象 和 其创建的子线程 的生命周期不是绑定的,有可能std::thread对象在栈中已经被析构销毁但是子线程还在运行,有可能子线程已经结束退出但是std::thread对象还存在;
  • 父线程创建子线程后必须要显式决定是join或者detach:
    • 选择join,立刻阻塞父线程,等待子线程完成后,父线程再继续运行,并由父线程回收子线程的相关资源;
    • 选择detach,分离子线程,子线程在后台运行(这种叫做守护线程),子线程的所有权和控制权转交给C++ runtime,子线程退出后由C++ runtime回收子线程的相关资源; 父线程的std::thread对象析构后,父线程不能够通过std::thread对象名与子线程通信;
    • C++ 标准库的线程管理要求每个线程对象必须有一个明确的处理方式,要么被加入到主线程中,要么被分离; 否则线程对象生命周期结束时会触发未定义行为,通常表现为程序终止;《C++并发编程实战》第二版解释:“std::thread 的析构函数会调用 std::terminate(),即便是有异常存在,也需要确保线程能够正确汇入(joined)或分离(detached)。”
    • 一旦子线程被detach,就不能够再被join,否则程序就会崩溃;可以使用joinable()函数判断一个线程对象能否调用join();

下文是代码验证,版本是g++ (GCC) 4.9.2。

join效果代码示例

#include <iostream>
#include <thread>

void threadTask() {
    std::this_thread::sleep_for(std::chrono::milliseconds(5000)); 
    std::cout << "child thread" << std::endl;
}

void func() {
  std::cout << "call func0" << std::endl;
  std::thread t1(threadTask);
  std::cout << "call func1, " << t1.joinable() <<  std::endl;
  t1.join();
  std::cout << "call func2, " << t1.joinable() <<  std::endl;
}

int main() {
    func();
    std::cout << "main thread" << std::endl;
    return 0;
}

/*
编译和输出
g++ t1.cpp -o t1 --std=c++11  -lpthread
./t1
call func0
call func1, 1
child thread
call func2, 0
main thread
*/

detach代码示例

#include <iostream>
#include <thread>

void threadTask() {
    std::this_thread::sleep_for(std::chrono::milliseconds(5000)); 
    std::cout << "hello thread" << std::endl; // 子线程运行到这里时,父线程已经退出,所以不会输出到终端里
}

void func() {
  std::cout << "call func0" << std::endl;
  std::thread t1(threadTask);
  std::cout << "call func1, " << t1.joinable() <<  std::endl;
  t1.detach(); // detach后,t1所在的函数栈结束导致t1析构,但是子线程还能继续存在
  std::cout << "call func2, " << t1.joinable() <<  std::endl;
}

int main() {
    func();
    
    std::cout << "main thread" << std::endl;
    return 0;
}

/*
编译和输出
g++ t2.cpp -o t2 --std=c++11  -lpthread
./t2
call func0
call func1, 1
call func2, 0
main thread
*/

既不join也不detach会发生什么?

#include <iostream>
#include <thread>

void threadTask() {
    std::cout << "child thread" << std::endl;
}

void func() {
  std::cout << "call func0" << std::endl;
  std::thread t1(threadTask);
  std::cout << "call func1, " << t1.joinable() <<  std::endl;
}

int main() {
    func();
    std::cout << "main thread" << std::endl;
    return 0;
}

/*
编译和输出
g++ t3.cpp -o t3 --std=c++11  -lpthread

./t3
call func0
call func1, 1child thread

terminate called without an active exception

Aborted (core dumped)

./t3
call func0
call func1, 1
child thread
terminate called without an active exception
Aborted (core dumped)
*/
*/
posted @ 2024-12-19 10:55  忆万富翁  阅读(1116)  评论(0)    收藏  举报