You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C++14中优雅实现C++20 std::barrier的等效行为?

在C14/C17中实现类似C++20 std::barrier的功能

我明白你需要兼容VS2015(只能用C14)同时也想了解C17的实现方案,毕竟VS2019可以开C20但老项目得兼顾旧编译器。下面我就分别给出两种标准下的优雅实现,完全对标C20 std::barrier的核心行为。


C++14 实现(兼容VS2015)

C++14里没有原生的屏障机制,但我们可以用std::mutex、std::condition_variable和原子变量手动实现一个可循环使用的Barrier,核心逻辑和std::barrier完全对齐:指定线程数,所有线程调用arrive_and_wait()后阻塞,直到全部到达;最后一个到达的线程执行完成函数,随后重置屏障并唤醒所有线程,支持重复使用。

完整实现代码

#include <mutex>
#include <condition_variable>
#include <atomic>
#include <functional>

class Barrier {
public:
    // 构造函数:指定线程数和可选的完成函数
    explicit Barrier(std::ptrdiff_t count, std::function<void()> completion_func = {})
        : expected_count_(count),
          current_count_(count),
          generation_(0),
          completion_func_(std::move(completion_func)) {}

    // 核心方法:线程到达屏障并等待
    void arrive_and_wait() {
        std::unique_lock<std::mutex> lock(mutex_);
        const auto current_generation = generation_;

        // 当前线程到达,计数减一
        if (--current_count_ == 0) {
            // 所有线程都到了,执行完成函数
            if (completion_func_) {
                completion_func_();
            }
            // 重置计数、更新代次(避免虚假唤醒)、唤醒所有线程
            current_count_ = expected_count_;
            ++generation_;
            cv_.notify_all();
        } else {
            // 等待直到当前"代"的屏障完成
            cv_.wait(lock, [this, current_generation] {
                return generation_ != current_generation;
            });
        }
    }

private:
    std::mutex mutex_;
    std::condition_variable cv_;
    const std::ptrdiff_t expected_count_; // 固定的线程总数
    std::ptrdiff_t current_count_;        // 当前未到达的线程数
    std::size_t generation_;              // 代次标记,处理循环使用的情况
    std::function<void()> completion_func_; // 完成回调
};

关键细节说明

  • 代次标记generation_:这是实现循环复用的关键。每次屏障完成后,代次加一,等待的线程通过判断代次变化来确定是否可以继续,避免了虚假唤醒或者重复等待的问题。
  • 线程安全:所有方法都通过互斥锁保护,支持多线程并发调用。
  • VS2015兼容性:用到的所有标准库组件(mutex、condition_variable、function、atomic)在VS2015中都完全支持,只需要将项目编译标准设置为/std:c++14即可。

使用示例(对标std::barrier的典型场景)

#include <thread>
#include <iostream>
#include <vector>

int main() {
    const int num_threads = 3;
    int shared_data = 0;

    // 创建屏障,指定3个线程,完成函数打印共享数据并更新
    Barrier barrier(num_threads, [&] {
        std::cout << "All threads arrived! Shared data: " << shared_data << "\n";
        shared_data += 10;
    });

    std::vector<std::thread> threads;
    for (int i = 0; i < num_threads; ++i) {
        threads.emplace_back([&, i] {
            for (int round = 0; round < 2; ++round) {
                std::cout << "Thread " << i << " working on round " << round << "\n";
                shared_data += 1;
                // 到达屏障,等待其他线程
                barrier.arrive_and_wait();

                // 屏障完成后继续执行
                std::cout << "Thread " << i << " continuing after round " << round << "\n";
            }
        });
    }

    // 等待所有线程结束
    for (auto& t : threads) {
        t.join();
    }

    std::cout << "Final shared data: " << shared_data << "\n";
    return 0;
}

运行这个示例,你会看到和C++20 std::barrier完全一致的输出:每一轮所有线程完成工作后,执行完成函数,然后所有线程继续下一轮。


C++17 实现方案

C17依然没有原生的可复用屏障(只有一次性的std::latch),所以核心逻辑还是和C14版本类似,但可以利用C++17的新特性让代码更灵活:

优化后的实现代码

#include <mutex>
#include <condition_variable>
#include <atomic>
#include <functional>
#include <utility>
#include <type_traits>
#include <functional> // 用于std::invoke

class Barrier {
public:
    // 模板构造函数:接受任意可调用对象作为完成函数,避免std::function的拷贝开销
    template<typename CompletionFunc, typename = std::enable_if_t<std::is_invocable_v<CompletionFunc>>>
    explicit Barrier(std::ptrdiff_t count, CompletionFunc&& completion_func)
        : expected_count_(count),
          current_count_(count),
          generation_(0),
          completion_func_(std::forward<CompletionFunc>(completion_func)) {}

    // 无完成函数的构造函数
    explicit Barrier(std::ptrdiff_t count) : Barrier(count, []{}) {}

    void arrive_and_wait() {
        std::unique_lock<std::mutex> lock(mutex_);
        const auto gen = generation_;

        if (--current_count_ == 0) {
            // C++17的std::invoke:支持调用任意可调用对象(lambda、函数指针、成员函数等)
            std::invoke(completion_func_);
            current_count_ = expected_count_;
            ++generation_;
            cv_.notify_all();
        } else {
            cv_.wait(lock, [this, gen] { return generation_ != gen; });
        }
    }

private:
    std::mutex mutex_;
    std::condition_variable cv_;
    const std::ptrdiff_t expected_count_;
    std::ptrdiff_t current_count_;
    std::size_t generation_;
    std::function<void()> completion_func_;
};

C++17特性的应用

  • 模板构造函数:直接接受任意可调用对象,避免了std::function的不必要拷贝,性能更优。
  • std::invoke:统一了可调用对象的调用方式,不管是普通函数、lambda还是成员函数指针,都能正确执行。
  • std::enable_if_t和std::is_invocable_v:在编译期检查传入的完成函数是否可调用,提前发现错误。

这个版本的行为和C14版本完全一致,但代码更现代、更灵活,适合在支持C17的环境中使用。


内容的提问来源于stack exchange,提问作者All The Rage

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 20:33:09