用一个函数展示C++20 std::latch、std::barrier和完成回调函数的使用方法

最近了解了一下C++20新加入的std::latch(门闩)和std::barrier(屏障),它们是用来在多线程程序中让线程之间进行协调的机制,具体可以在网上搜索或者问AI,这里就不展开了。

本文主要是想在一个函数里把这两个机制都用上,以使读者有更直观的感受。本文还用到了std::barrier的完成回调函数机制,它会在每一次所有线程都到达屏障的时候执行。

程序的主体部分是ChatGPT(o3-mini)写的,然后改了一下bug。一开始ChatGPT写出来的程序对于worker函数签名里的barrier类型声明有误,漏了barrier的模板参数,问了kimi半天没解决问题,问了DeepSeek一下子就找到问题所在了,可惜DeepSeek太卡了,要重试个十几二十几次才能回答。

一些细节的讨论在程序的注释里。

话不多说,上代码!

代码

编译的时候需要设置语言版本为C++23(-std=c++23)。
本程序在Windows上使用GCC 14.2.0(MinGW-W64)测试通过。也在Compiler Explorer上使用clang (trunk)和gcc (trunk)测试通过。
程序的每一条std::println旁边都有一条std::osyncstream(std::cout)(要使用的话要#include <syncstream>),它们的效果是一样的。但是如果直接使用std::cout的话,则会出现多线程输出内容交织的情况。

#include <iostream>
#include <print>
#include <vector>
#include <thread>
#include <latch>
#include <barrier>
#include <chrono>
// #include <syncstream>
#include <functional>

// 为了使用 1s、1000ms 这样的用户自定义字面量,这样更直观
using namespace std::chrono_literals;

// 完成回调函数可以定义成 lambda 函数(第一行),也可以包装在 std::function 里(第二行)
// 如果是前者,那么下面 worker 的签名也要用第一行的,后者的话下面 worker 的签名第一行和第二行的都可以
// 用 std::function 的好处在于,completion_function 不一定要定义在 worker 前面
// 如果用 lambda 函数,那么 worker 的签名要用到 decltype(completion_function) ,
// 就需要 completion_function 定义在 worker 前面
// auto completion_function = []
auto completion_function = std::function([]
{
    // std::osyncstream(std::cout) << "All threads reached the barrier. Proceeding to next phase.\n";
    std::println("All threads reached the barrier. Proceeding to next phase.");
});

// std::barrier 的模板参数是完成回调函数的类型,如果要用到完成回调函数,那么不可省略
// void worker(std::latch &latch, std::barrier<decltype(completion_function)> &barrier, int threadID)
void worker(std::latch &latch, std::barrier<std::function<void()>> &barrier, int threadID)
{
    // 模拟初始工作(不同线程耗时不同)
    // std::osyncstream(std::cout) << "Thread " << threadID << " is performing initial work.\n";
    std::println("Thread {} is performing initial work.", threadID);
    std::this_thread::sleep_for(1000ms * threadID);
    // std::osyncstream(std::cout) << "Thread " << threadID << " finished initial work, counting down latch.\n";
    std::println("Thread {} finished initial work, counting down latch.", threadID);

    // latch 部分:每个线程调用 count_down() 后等待
    latch.count_down();
    latch.wait();

    // std::osyncstream(std::cout) << "Thread " << threadID << " passed latch synchronization.\n";
    std::println("Thread {} passed latch synchronization.", threadID);

    // barrier 部分:进行3个阶段的同步
    for (int phase = 1; phase <= 3; phase++) {
        // std::osyncstream(std::cout) << "Thread " << threadID << " is working on barrier phase " << phase << ".\n";
        std::println("Thread {} is working on barrier phase {}.", threadID, phase);
        std::this_thread::sleep_for(1000ms);
        // std::osyncstream(std::cout) << "Thread " << threadID << " reached barrier phase " << phase << ".\n";
        std::println("Thread {} reached barrier phase {}.", threadID, phase);
        barrier.arrive_and_wait();
    }
}

int main()
{
    const int num_threads = 3;

    // 创建 latch 对象,计数器初值为线程数量
    std::latch latch(num_threads);

    // 创建 barrier 对象,参与线程数为 num_threads,设置回调函数在每个阶段结束时执行
    std::barrier barrier(num_threads, completion_function);

    std::vector<std::jthread> threads;
    // 创建 num_threads 个线程,全部执行 worker 函数
    for (int i = 0; i < num_threads; i++) {
        threads.emplace_back(worker, std::ref(latch), std::ref(barrier), i + 1);
    }

    // std::jthread 会在析构的时候自动 join(),不需要显式地 join()
    // for (auto &t : threads) {
    //     t.join();
    // }

    return 0;
}

样例输出

供参考,其中不同线程之间输出行的顺序是没有保证的,每一次运行都是不一样的。

Thread 1 is performing initial work.
Thread 3 is performing initial work.
Thread 2 is performing initial work.
Thread 1 finished initial work, counting down latch.
Thread 2 finished initial work, counting down latch.
Thread 3 finished initial work, counting down latch.
Thread 3 passed latch synchronization.
Thread 1 passed latch synchronization.
Thread 1 is working on barrier phase 1.
Thread 2 passed latch synchronization.
Thread 2 is working on barrier phase 1.
Thread 3 is working on barrier phase 1.
Thread 1 reached barrier phase 1.
Thread 2 reached barrier phase 1.
Thread 3 reached barrier phase 1.
All threads reached the barrier. Proceeding to next phase.
Thread 2 is working on barrier phase 2.
Thread 3 is working on barrier phase 2.
Thread 1 is working on barrier phase 2.
Thread 2 reached barrier phase 2.
Thread 3 reached barrier phase 2.
Thread 1 reached barrier phase 2.
All threads reached the barrier. Proceeding to next phase.
Thread 1 is working on barrier phase 3.
Thread 3 is working on barrier phase 3.
Thread 2 is working on barrier phase 3.
Thread 1 reached barrier phase 3.
Thread 3 reached barrier phase 3.
Thread 2 reached barrier phase 3.
All threads reached the barrier. Proceeding to next phase.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值