第 18.19.1 節
std::thread 與 join
0瀏覽次數0訪問次數--跳出率--平均停留
本節解決什麼問題
std::thread 用來創建線程。線程創建後會立刻啟動,主線程可以繼續執行自己的代碼。
初學階段最重要的是記住:std::thread 對象銷燬前,必須調用 join() 或 detach()。多數教學和普通業務代碼裡優先用 join(),因為它會等待線程結束,生命週期最清楚。
示例代碼
示例 1:創建兩個線程分別處理兩份數據
這個例子中,兩個線程分別寫入不同的 vector,所以沒有共享寫同一個容器,不需要加鎖。主線程等兩個線程都結束後再打印結果。
#include <functional>
#include <iostream>
#include <thread>
#include <vector>
// vector 是动态数组,元素数量可以在运行时变化。
void fill_numbers(std::vector<int>& output, int start)
{
for (int i = 0; i < 3; ++i)
{
output.push_back(start + i);
}
}
void print_numbers(const char* name, const std::vector<int>& values)
{
std::cout << name << ": ";
for (int value : values)
{
std::cout << value << " ";
}
std::cout << "\n";
}
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
std::vector<int> left;
std::vector<int> right;
// 创建子线程,让这部分代码和 main 线程并发运行。
std::thread t1(fill_numbers, std::ref(left), 1);
std::thread t2(fill_numbers, std::ref(right), 10);
// join 会等待子线程结束,避免 main 提前退出。
t1.join();
t2.join();
print_numbers("left", left);
print_numbers("right", right);
std::cout << "done\n";
return 0;
}
運行結果:
left: 1 2 3
right: 10 11 12
done
這裡使用 std::ref(left) 和 std::ref(right),表示把 vector 按引用傳給線程函數。否則 std::thread 會嘗試複製參數。
示例 2:順序等待 vs 併發等待
一個任務等待 1 秒,順序寫法和線程寫法看起來都只是"等完了"。兩個任務各等 1 秒時,區別就明顯了:
- 順序執行:先等 A,再等 B,總共約 2 秒。
- 兩個線程:A 和 B 同時等,總共約 1 秒。
#include <chrono>
#include <iostream>
#include <thread>
using namespace std::chrono;
void wait_one_second()
{
std::this_thread::sleep_for(seconds(1));
}
long long run_sync()
{
auto begin = steady_clock::now();
wait_one_second();
wait_one_second();
auto end = steady_clock::now();
return duration_cast<seconds>(end - begin).count();
}
long long run_threads()
{
auto begin = steady_clock::now();
// 创建子线程,让这部分代码和 main 线程并发运行。
std::thread t1(wait_one_second);
std::thread t2(wait_one_second);
// join 会等待子线程结束,避免 main 提前退出。
t1.join();
t2.join();
auto end = steady_clock::now();
return duration_cast<seconds>(end - begin).count();
}
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
std::cout << "sync seconds = " << run_sync() << "\n";
std::cout << "thread seconds = " << run_threads() << "\n";
return 0;
}
運行結果:
sync seconds = 2
thread seconds = 1
這個例子不是說所有任務都應該開線程。線程創建和調度也有成本。非常短的任務,例如只做幾次加法,開線程可能反而更慢。
常見錯誤
錯誤 1:忘記 join 或 detach
{
std::thread t([] {
std::cout << "work\n";
});
} // t 还是 joinable,程序会 std::terminate
正確做法:在 t 離開作用域前調用 t.join()。
錯誤 2:線程函數引用了已經銷燬的局部變量
void start_thread()
{
int value = 42;
std::thread t([&value] {
std::cout << value << "\n";
});
t.detach();
}
detach() 後線程可能晚於 start_thread() 返回才執行,此時 value 已經銷燬。正確做法是按值捕獲,或者用智能指針管理生命週期。
小結
std::thread創建後會立刻啟動。join()表示等待線程完成。- 初學階段優先用
join(),少用detach()。 - 向線程函數傳引用參數時要用
std::ref。 - 兩個耗時任務同時推進時,才能直觀看出併發的價值。