第 18.19.1 節

std::thread and join

0瀏覽次數0訪問次數--跳出率--平均停留

What problem does this section solve?

std::thread is used to create a thread. Once created, the thread starts immediately, and the main thread can continue executing its own code.

When starting out, the most important thing to remember is: before destroying the std::thread object, you must call either join() or detach(). In most tutorials and typical business code, join() is preferred because it waits for the thread to finish, making the lifecycle clearest.

Example code

Example 1: Create two threads to process two sets of data

In this example, two threads write to different vector containers, so there's no shared write access to the same container and no locking is needed. The main thread waits for both threads to finish before printing the results.

#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;
}

Results

left: 1 2 3
right: 10 11 12
done

Here we use std::ref(left) and std::ref(right) to pass vector by reference to the thread function. Otherwise, std::thread will attempt to copy the parameter.

Example 2: Sequential Waiting vs Concurrent Waiting

When a single task waits for 1 second, both the sequential writing and thread writing seem to simply "finish waiting." However, when two tasks each wait for 1 second, the difference becomes clear:

  • Sequential execution: wait for A, then wait for B, taking approximately 2 seconds in total.
  • Two threads: A and B wait simultaneously, for a total of about one second.
#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;
}

Results

sync seconds = 2
thread seconds = 1

This example doesn't mean that all tasks should use threads. Thread creation and scheduling also have overhead. For very short tasks, such as performing just a few additions, creating a thread might actually be slower and counterproductive.

Common Errors

Error 1: Forgetting join or detach

{
    std::thread t([] {
        std::cout << "work\n";
    });
} // t 还是 joinable,程序会 std::terminate

The correct approach is to call t.join() before t leaves scope.

Error 2: Thread function references destroyed local variables

void start_thread()
{
    int value = 42;
    std::thread t([&value] {
        std::cout << value << "\n";
    });
    t.detach();
}

The thread may execute later than start_thread()'s return, by which time value might already be destroyed. The proper approach is to capture by value or manage the lifecycle using smart pointers.

Summary

  • std::thread will start immediately after creation.
  • join() indicates waiting for the thread to complete.
  • For beginners, prioritize using join() and minimize the use of detach().
  • When passing reference parameters to thread functions, use std::ref.
  • Only when two time-consuming tasks proceed simultaneously can one intuitively see the value of concurrency.
音乐页