第 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
  • 兩個耗時任務同時推進時,才能直觀看出併發的價值。
音乐页