c++ 线程创建

创建线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <thread>
#include <iostream>
void threadFunction()
{
std::cout << "threadFunction" << std::endl;
}

int main()
{
std::thread t(threadFunction);
t.join();
return 0;
}

问题1: C++ 中创建 std::thread 线程后是否必须调用 join()

答案是:不是必须调用 join(),但必须调用 join()detach() 二者之一;如果既不 join 也不 detach,线程对象析构时会触发程序崩溃(调用 std::terminate())。

一、核心原因:std::thread 的析构规则

std::thread 设计的核心原则是:不允许「悬空」的线程关联——当 std::thread 对象销毁时,如果线程还处于「可连接状态(joinable() == true)」(即未调用 join()/detach()),会直接调用 std::terminate() 终止整个程序,这是为了避免「线程还在运行,但管理它的对象已销毁」导致的资源泄漏或未定义行为。

反面示例(必崩溃):

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

void task() {
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "线程执行完毕" << std::endl;
}

int main() {
// 创建线程,但既不 join 也不 detach
std::thread t(task);

// 主线程直接退出,t 析构时发现线程仍可连接,触发 terminate()
std::cout << "主线程退出" << std::endl;
return 0;
}

执行结果:程序会崩溃,控制台可能输出 terminate called without an active exception,且不会打印「线程执行完毕」。

二、join() vs detach():两种合法处理方式

你可以根据业务场景选择其中一种,二者都是「合法收尾」,但语义完全不同:

方式 核心行为 适用场景
join() 阻塞当前线程(如主线程),直到子线程执行完毕;执行后线程变为「不可连接状态」。 需要等待子线程完成后再继续执行(比如子线程计算的结果主线程要使用)。
detach() 将子线程与 std::thread 对象分离,子线程变为「后台线程」,由系统接管资源;执行后线程变为「不可连接状态」。 不需要等待子线程完成(比如后台日志线程、心跳检测线程),主线程退出后子线程仍可运行。

1. join() 示例(等待线程完成)

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

void task() {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "子线程执行完成" << std::endl;
}

int main() {
std::thread t(task);
std::cout << "主线程等待子线程..." << std::endl;
t.join(); // 阻塞主线程,直到子线程结束
std::cout << "主线程继续执行" << std::endl;
return 0;
}

执行顺序:主线程打印「等待」→ 子线程执行 → 子线程打印 → 主线程打印「继续执行」。

2. detach() 示例(分离线程)

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>
#include <chrono>

void backgroundTask() {
for (int i = 0; i < 3; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "后台线程运行中:" << i << std::endl;
}
std::cout << "后台线程执行完毕" << std::endl;
}

int main() {
std::thread t(backgroundTask);
t.detach(); // 分离线程,主线程无需等待

std::cout << "主线程无需等待,先睡 2 秒..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "主线程退出" << std::endl;
return 0;
}

执行结果:主线程睡 2 秒后退出,后台线程可能还在运行(输出「后台线程运行中:2」),最终由系统回收其资源。

三、关键注意事项

1. detach() 的风险

  • 分离后的线程无法再通过 std::thread 对象控制,也无法获取其状态;
  • 如果主线程退出(进程结束),所有分离的子线程会被强制终止,无论是否执行完毕;
  • 分离线程不能访问主线程中已销毁的资源(比如主线程的局部变量),否则会导致未定义行为。

2. 如何判断是否需要 join()/detach()

可以通过 joinable() 方法判断线程是否处于「可连接状态」,避免重复调用 join()/detach()(重复调用会抛异常):

1
2
3
4
std::thread t(task);
if (t.joinable()) {
t.join(); // 仅当可连接时才调用
}

3. 异常安全的线程管理(RAII 封装)

如果主线程在 join() 前抛出异常,仍会导致线程未处理而崩溃。推荐用 RAII 封装线程,确保析构时自动处理:

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
28
29
30
31
#include <iostream>
#include <thread>

class ThreadGuard {
public:
explicit ThreadGuard(std::thread t) : m_thread(std::move(t)) {}

// 析构时自动处理线程
~ThreadGuard() {
if (m_thread.joinable()) {
m_thread.join(); // 也可根据需求改为 detach()
}
}

// 禁用拷贝(std::thread 不可拷贝)
ThreadGuard(const ThreadGuard&) = delete;
ThreadGuard& operator=(const ThreadGuard&) = delete;

private:
std::thread m_thread;
};

void task() {
// 模拟任务
}

int main() {
ThreadGuard tg(std::thread(task)); // 无需手动 join/detach,析构时自动处理
// 即使此处抛出异常,tg 析构时也会处理线程
return 0;
}

四、特殊情况:不创建实际线程的场景

如果 std::thread 对象创建时没有关联实际线程(比如默认构造、移动赋值后),则无需 join()/detach()

1
2
std::thread t; // 默认构造,无关联线程,joinable() == false
t = std::thread(task); // 移动赋值后,需处理 join/detach

总结

  1. 核心规则std::thread 创建后必须调用 join()detach(),否则析构时程序崩溃;
  2. 场景选择:需要等待线程结果/完成 → join();后台运行无需等待 → detach()
  3. 安全实践:用 joinable() 判断状态,通过 RAII 封装保证异常安全,避免手动管理的疏漏。

简单记:std::thread 的「可连接状态」是「必须收尾」的,join() 是「等它做完」,detach() 是「放它走」,二者选其一即可。