如何在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
相关产品推荐
相关产品推荐

