c++ jthread

自動join

std::threadでは、スレッドが終了する前にthreadオブジェクトが破棄されるとstd::terminate()が呼ばれる。そのため、join()を呼んで、スレッドの終了を待つ必要がある。 (スレッドの管理が必要ないのであれば、detach()でもよい)

TEST(thread, cpp20) {
    using namespace::std::literals;

    int x{0};
    std::thread t([&]() {
            std::cout << "thread start" << std::endl;
            while(x < 10){
                std::this_thread::sleep_for(0.5s);
                std::cout << "x:" << x++ << std::endl;
            }
            std::cout << "thread exit" << std::endl;
            });

    std::this_thread::sleep_for(2s);
    t.join(); // <<<<<<<< スレッドの終了を待つ
}

std::jthreadでは、jthreadオブジェクトが破棄される際にjoin()が自動的に呼ばれるので、明示的にjoin()を呼ぶ必要はない。

TEST(jthread, cpp20) {
    using namespace::std::literals;

    int x{0};
    std::jthread t([&]() {
            std::cout << "thread start" << std::endl;
            while(x < 10){
                std::this_thread::sleep_for(0.5s);
                std::cout << "x:" << x++ << std::endl;
            }
            std::cout << "thread exit" << std::endl;
            });

    std::this_thread::sleep_for(2s);
    // スレッドが終了するまで、自動的に待つ
}

スレッドのキャンセル

request_stop()の呼び出しで、スレッドをキャンセルできる。

TEST(jthread_cancel, cpp20) {
    using namespace::std::literals;

    int x = 0, y = 0;
    std::jthread t([&](std::stop_token token) {
            std::cout << "thread start" << std::endl;
            while(x < 10){
                // 停止要求があれば終了
                if(token.stop_requested()){
                    std::cout << "stop requested" << std::endl;
                    break;
                }
                std::this_thread::sleep_for(0.5s);
                std::cout << "x:" << x++ << std::endl;
            }
            std::cout << "thread exit" << std::endl;
            });

    // スレッドに対する停止要求で呼び出されるコールバック
    std::stop_callback sc { t.get_stop_token(), [&] {
        std::cout << "stop_callback" << std::endl;
        y++;
    }};
    ASSERT_EQ(x , 0);

    std::this_thread::sleep_for(2s);
    t.request_stop(); // スレッド停止要求

    ASSERT_EQ(y , 1);
}

std::latchによるスレッド同期

std::latchを使って、値が0になるまで待つ。

TEST(latch, cpp20) {
    using namespace::std::literals;

    const int num_threads = 5;
    std::latch l(num_threads);
    std::vector<std::jthread> threads;

    // 5つのスレッドを起動
    for(int i = 0 ; i < num_threads ; i++)
        threads.push_back(std::jthread([i, &l](){
            std::cout << "thread " << i << " start" << std::endl;
            std::this_thread::sleep_for(i * 1s);
            std::cout << "thread " << i << " exit" << std::endl;

            l.count_down(); // 1つ減らす
        }));

    l.wait(); // latchが0になるまで待つ
    std::cout << "all threads exit" << std::endl;
}

std::counter_semaphoreによる同時実行数の制限

std::counter_semaphoreを使って、特定の処理を同時に実行できるスレッドの数を制限できる。

TEST(semaphore, cpp20) {
    using namespace::std::literals;

    const int num_threads = 10;
    const int num_semaphre = 3;
    std::counting_semaphore semaphore(num_semaphre);
    std::vector<std::jthread> threads;
    std::atomic<int> counter;

    // 10のスレッドを起動
    for(int i = 0 ; i < num_threads ; i++)
        threads.push_back(std::jthread([i, num_semaphre, &semaphore, &counter](){
            semaphore.acquire(); // 空きを待つ
            std::cout << "counter=" << ++counter << std::endl;
            std::cout << "thread " << i << " acquired" << std::endl;
            ASSERT_LE(counter, num_semaphre); // 3以下のはず
            std::this_thread::sleep_for(i * 1s);
            counter--;
            std::cout << "thread " << i << " exit" << std::endl;
            semaphore.release(); // セマフォ開放
        }));
}