「C++ 多线程」join(), detach(), joinable()

文章大图来源: pixiv_id=82332929

1 主线程与子线程

在多线程程序中,通常情况下有一个主线程以及若干个子线程。主线程就是我们执行主函数 main() 的线程,子线程是我们在主线程的函数 main() 中(或者可以在其他现有线程的函数中中)创建的线程。

主线程和子线程在同一时间段内同时进行,是并发(并行)运行的。在这个过程中,会有以下几种情况:

  • 主线程先运行结束

  • 子线程先运行结束

  • 主线程与子线程同时结束

有时候我们需要让子线程运行结束,主线程才能结束;有时我们可以选择不等待子线程运行结束,主线程运行完后整个程序就可以结束了。

需要注意的是,在不等待子线程运行结束的情况下,主线程运行结束后子线程并不会立即停止,而是 会进入后台运行。如果子线程所需资源(如内存)仍然有效,并且没有被外部因素(如其他线程发送终止信号等)强制终止,子线程还是会继续运行。只不过是在后台运行。

2 std::thread::join

join() 是 std::thread 类的一个成员函数,作用是等待线程完成。我们在主线程中创建一个子线程 t,并在这个线程对象上调用 join() 函数,此时主线程会被 阻塞,直到调用 join() 函数的子线程 t 执行完。

我们可以写一个简单的线程接口函数 foo(),并在其中模拟做了一些事情。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <thread>

void foo(){
std::cout << "foo(): thread starts." << "\n";
// 模拟做一些事情
std::cout << "foo(): MarisaMagic is doing sth." << "\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "foo(): thread ends." << "\n";
}

int main(){
std::cout << "main(): thread starts." << "\n";

std::thread t(foo);
t.join(); // 主线程等待子线程运行结束

std::cout << "main(): thread ends." << "\n";

return 0;
}

运行结果如下。可以看出,foo() 子线程运行结束后,main() 主线程才结束。重复多次运行后结果依旧如此。

在上面的程序中,为了让子线程运行时间长一些,方便之后更好演示,子线程接口函数中使用了 std::this_thread::sleep_for() 函数,作用是让当前线程休息一段时间。std::this_thread::sleep_for() 接收的是一个 std::chrono::duration 类型的参数(通常为秒(std::chrono::seconds)、毫秒(std::chrono::milliseconds)等)。此处,我让子线程休息了 1 秒钟。

3 std::thread::detach

detach() 也是 std::thread 类的一个成员函数,称为 分离线程函数。我们在主线程中创建一个子线程 t,并在这个线程对象上调用 detach() 函数后,main() 主线程不会等待子线程 t 运行结束。

主线程执行可能是比较快的,会先于子线程结束,而分离的子线程 t 可以在后台独立运行,和主线程相互独立。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <thread>

void foo(){
std::cout << "foo(): thread starts." << "\n";
// 模拟做一些事情
std::cout << "foo(): MarisaMagic is doing sth." << "\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "foo(): thread ends." << "\n";
}

int main(){
std::cout << "main(): thread starts." << "\n";

std::thread t(foo);
t.detach(); // 主线程不等待子线程运行结束

std::cout << "main(): thread ends." << "\n";

return 0;
}

运行结果如下。可以看出,主线程运行得比较快,而子线程需要 1 秒多的运行时间,主线程并没有等待子线程运行结束就已经先结束了。

4 join() 和 detach() 不可多次调用

当一个子线程调用了一次 join() 后,就不能再次调用 join() 了。join() 是等待线程执行完成,执行完之后,子线程也自然结束,再次调用 join() 是不可行的,当然调用 detach() 也没有意义,会导致未定义行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <thread>

void foo(){
std::cout << "foo(): thread starts." << "\n";
// 模拟做一些事情
std::cout << "foo(): MarisaMagic is doing sth." << "\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "foo(): thread ends." << "\n";
}

int main(){
std::cout << "main(): thread starts." << "\n";

std::thread t(foo);
t.join();

try{
t.join(); // 不可多次join()(再次detach()也不行)
}catch(...){
std::cout << "ERROR: multiple join." << "\n";
}

std::cout << "main(): thread ends." << "\n";

return 0;
}

同理,一个子线程调用了一次 detach() 被分离后,也不能再通过 std::thread 对象来对该线程进行控制(不能再次调用 join()detach())。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <thread>

void foo(){
std::cout << "foo(): thread starts." << "\n";
// 模拟做一些事情
std::cout << "foo(): MarisaMagic is doing sth." << "\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "foo(): thread ends." << "\n";
}

int main(){
std::cout << "main(): thread starts." << "\n";

std::thread t(foo);
t.detach();

try{
t.detach(); // 不可多次detach()(再次join()也不可行)
}catch(...){
std::cout << "ERROR: multiple detach." << "\n";
}

std::cout << "main(): thread ends." << "\n";

return 0;
}

5 std::thread::joinable

joinable() 是 std::thread 类的一个成员函数,用于检查线程是否可以被 join 或者 detach。如果线程是可以调用 join 或者 detach 的,那么 joinable() 函数返回值为 true;否则为 false

一个线程是不可 join 也不可 detach 的(joinable() 函数返回值为 false,状态为 nonjoinable),大致分为三种情况:

  • 线程还没有开始执行。(例如默认构造的 std::thread 对象,没有关联任何接口函数)

  • 线程已经被 join(等待并执行完毕)

  • 线程已经被 detach(已经被分离了)

我们可以用如下程序进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <thread>

void foo(){ return; }

int main(){
std::thread t1; // 未关联任何函数
std::cout << t1.joinable() << "\n";

std::thread t2(foo);
t2.join(); // 已经join
std::cout << t2.joinable() << "\n";

std::thread t3(foo);
t3.detach(); // 已经detach
std::cout << t3.joinable() << "\n";

return 0;
}

输出三个 0,返回值均为 false,表示三种情况下线程都是不可 join 也不可 detach 的。

在实际运用过程中,为了保险起见,我们有时候可以在对线程进行 join 或者detach 操作之前先检查是否 joinable,避免出现未定义行为。

1
2
3
4
std::thread t(foo);
if(t.joinable()){ // 检查是否可以进行join或detach
t.join();
}

参考

  1. cplusplus reference std::thread

  2. C++11多线程join()和detach()的理解

  3. C++11 线程对象创建后既不join()也不detach()的后果


「C++ 多线程」join(), detach(), joinable()
https://marisamagic.github.io/2024/12/18/20241218/
作者
MarisaMagic
发布于
2024年12月18日
许可协议