浅谈c++如何实现并发中的Barrier

浅谈 C++ 如何实现并发中的 Barrier(屏障)。

什么是 Barrier

Barrier 是一种线程间的同步机制,用于在多个线程执行过程中,所有线程都执行到某一点后,才允许线程继续往下走。这样可以保证线程的执行顺序和结果的正确性。

常见的应用场景包括:并行计算(等待所有线程都计算完毕后合并结果)、多线程写入(等待所有线程都写入结束再合并文件)、游戏引擎中限制所有玩家必须等待约定的时间才开始游戏、高性能计算中同多个算法进行通信等。

具体实现上,当一个线程到达 Barrier 时,它将被阻塞,直至所有其它线程都到达 Barrier,然后同时被释放,继续执行后续任务。

C++ 如何实现 Barrier

C++ 中的屏障 Barrier 是由 C++11 标准引入的。它定义在头文件 <barrier> 中。在类 std::barrier 中,我们可以设置需要等待的屏障的总数,以及每个屏障的执行操作。该类的定义如下:

class barrier {
public:
    explicit barrier(std::size_t num_threads) noexcept(noexcept(std::size_t(std::declval<num_threads>())));

    template<class F> // F: void()
    explicit barrier(std::size_t num_threads, F);  // 指定每次屏障要执行的回调函数

    barrier(const barrier &) = delete;
    barrier(barrier &&) = delete;

    barrier & operator=(const barrier &) = delete;
    barrier & operator=(barrier &&) = delete;

    ~barrier();

    void wait(); // 每个线程调用 wait() 进入屏障等待
};

其中:
- num_threads 表示需要等待的屏障的总数;
- F 表示每个屏障要执行的回调函数,是一个无参无返回值的函数对象。

在这个类中,我们可以使用 std::barrier 实现 C++ 中的 Barrier 。

下面是一个示例:在 C++20 中多线程排序中使用屏障。实现多个线程同时排序不同片段,等待排序结束后再合并结果。其中,屏障的大小与线程数量相等。

#include <algorithm>
#include <barrier>
#include <chrono>
#include <iostream>
#include <vector>

template<typename RandomIt>
void parallel_sort(RandomIt first, RandomIt last,
                   std::size_t num_threads = std::thread::hardware_concurrency()) {
    auto sz = std::distance(first, last);
    if (sz <= 1) return;

    std::vector<std::thread> threads(num_threads - 1);

    std::vector<typename RandomIt::value_type> buffers[num_threads];
    std::vector<typename RandomIt::value_type> *buf[2] = {&buffers[0], &buffers[1]};
    std::size_t front = 0, back = 1;

    auto per_thread = sz / num_threads;
    auto left_over = sz % num_threads;

    std::vector<std::barrier> barriers(num_threads, num_threads);

    auto thread_func = [&](std::size_t id) {
        auto begin = first + id * per_thread;
        auto end = begin + per_thread;
        if (id == num_threads - 1) end = last;

        std::sort(begin, end);

        auto local_size = std::distance(begin, end);
        buf[front] = &buffers[id];
        buf[back] = &buffers[(id + 1) % num_threads];

        buf[front]->resize(local_size);

        std::move(begin, end, buf[front]->begin());

        std::size_t level = 0;
        std::size_t level_size = 2;

        for (std::size_t i = 0; i < barriers.size() - 1; ++i) {
            level_size <<= 1;
            if ((id & level) == 0 && ((id | level_size) < barriers.size())) {
                barriers[id].wait();
                std::inplace_merge(buf[front]->begin(), buf[front]->end(),
                                   buf[back]->begin(), buf[back]->end());
                std::swap(buf[front], buf[back]);
            }

            level_size -= (level += 1);
        }

        if (id != 0) barriers[id].wait();

        std::move(buf[front]->begin(), buf[front]->end(), begin);
    };

    for (std::size_t i = 0; i < num_threads - 1; ++i) {
        threads[i] = std::thread(thread_func, i);
    }

    thread_func(num_threads - 1);

    for (std::size_t i = 0; i < num_threads - 1; ++i) {
        threads[i].join();
    }

    buf[front] = &buffers[0];
    buf[back] = &buffers[1];

    for (std::size_t i = 1; i < num_threads; ++i) {
        std::inplace_merge(first, first + per_thread * i,
                           first + per_thread * (i + 1), last);
        buf[front]->resize(per_thread * (i + 1));
        std::move(first, first + per_thread * (i + 1), buf[front]->begin());
        std::swap(buf[front], buf[back]);
    }

    std::inplace_merge(first, first + per_thread * left_over,
                       last, last);
}

还可以实现一个简单的例子:在 C++17 中使用两个线程实现一个简单的屏障。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

class Barrier {
public:
    explicit Barrier(std::size_t num_threads) : num_threads_{num_threads} {}

    void wait() {
        std::unique_lock<std::mutex> lock(mu_);
        ++num_arrived_;
        if (num_arrived_ == num_threads_) {
            num_arrived_ = 0;
            cv_.notify_all();
        } else {
            cv_.wait(lock, [this] { return num_arrived_ == 0; });
        }
    }

private:
    std::mutex mu_;
    std::condition_variable cv_;
    std::size_t num_threads_;
    std::size_t num_arrived_ = 0;
};

int main() {
    const std::size_t num_threads = 2;
    Barrier barrier(num_threads);

    std::thread t1([&] {
        std::cout << "Thread 1 arrived." << std::endl;
        barrier.wait();
        std::cout << "Thread 1 continues." << std::endl;
    });

    std::thread t2([&] {
        std::cout << "Thread 2 arrived." << std::endl;
        barrier.wait();
        std::cout << "Thread 2 continues." << std::endl;
    });

    t1.join();
    t2.join();

    return 0;
}

以上两个示例可以在 C++17 中执行。一个示例使用 C++20 中的屏障实现多线程排序,另一个使用 C++17 编写的一个简单的屏障,其中有两个线程。

总结

以上就是关于 C++ 中如何实现并发中的 Barrier 的详细讲解,包含了 std::barrier 的使用方法和两个示例。建议在实际项目中使用 std::barrier 实现并发 Barrier,这将提高并发程序的效率和稳定性。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:浅谈c++如何实现并发中的Barrier - Python技术站

(0)
上一篇 2023年5月16日
下一篇 2023年5月16日

相关文章

  • java线程的基础实例解析

    Java线程的基础实例解析 什么是Java线程? Java线程是Java程序并发执行时最基本的执行单元。Java线程可以独立完成一定的任务,也可以与其他线程协作完成更复杂的任务。 Java线程的使用可以提升程序的性能,尤其适用于多核处理器系统。Java线程也是Java并发编程的重要部分,掌握Java线程编程技巧对于Java开发是非常重要的。 创建Java线程…

    多线程 2023年5月17日
    00
  • Java多线程回调方法实例解析

    Java多线程回调方法实例解析 什么是回调方法 在Java中,回调方法是指将一个方法作为参数传递给另一个方法,并在另一个方法执行后,调用传入的方法。这种方式可以让我们将一个方法的执行结果传递给另一个方法,从而实现代码的复用和解耦。 为什么要使用多线程回调方法 在多线程编程中,需要处理并发执行的任务。一个任务执行完成后,需要通知其他任务执行相关的代码,这时就需…

    多线程 2023年5月17日
    00
  • Java线程并发工具类CountDownLatch原理及用法

    Java线程并发工具类CountDownLatch原理及用法 简介 CountDownLatch是一种非常实用的java线程同步工具类,主要作用是允许一个或多个线程一直等待,在其他线程执行完一组操作之后才执行。 CountDownLatch主要有两个方法:* countDown() : 对计数器进行操作,将计数器的值减少1* await() : 调用该方法的…

    多线程 2023年5月16日
    00
  • Go语言实现一个简单的并发聊天室的项目实战

    下面我将为你详细讲解“Go语言实现一个简单的并发聊天室的项目实战”的完整攻略。 1. 确定项目需求 在开始我们的项目之前,需要先明确项目需求。这是任何项目开始之前都必须要做的。在聊天室项目中,我们需要实现以下需求: 支持多个用户同时在线 用户能够发送消息到聊天室中 用户能够接收到来自其他用户的消息 用户能够退出聊天室 2. 设计数据结构 在开始编写代码之前,…

    多线程 2023年5月17日
    00
  • php curl批处理实现可控并发异步操作示例

    下面是“php curl批处理实现可控并发异步操作示例”的完整攻略。 1. 准备工作 在开始之前,需要确保系统已经安装了curl扩展。可以通过以下命令来检查: php -m | grep curl 如果输出了curl,说明扩展已经安装成功。 2. 单个请求示例 首先来看一个简单的单个请求示例。代码如下: // 初始化curl $ch = curl_init(…

    多线程 2023年5月16日
    00
  • Java通过卖票理解多线程

    让我来为你详细讲解 “Java通过卖票理解多线程”的完整攻略。 为什么要通过卖票理解多线程? 卖票可以被用来直观的说明并发问题。多线程是一种并发编程的方式,由于线程之间共享进程内存,会导致并发问题,如竞争条件和死锁等,卖票问题可以很好的说明这些问题。 多线程卖票问题的本质是多个线程并发运行时操作共享数据的问题。理解和使用Java的多线程需要掌握线程并发运行的…

    多线程 2023年5月17日
    00
  • golang并发编程的实现

    Golang并发编程的实现完整攻略 Golang是一门强大的并发编程语言,提供了一系列的并发编程工具来帮助我们更容易地进行并发编程。在本文中,我们将介绍Golang并发编程的基础知识,以及如何使用Golang的goroutine、channel和select语句来实现并发编程。 并发编程基础 并发编程是指同时执行多个任务的编程方式。Golang提供了goro…

    多线程 2023年5月17日
    00
  • Java线程编程中Thread类的基础学习教程

    Java线程编程中Thread类的基础学习教程 什么是Java线程? 在计算机科学中,线程是进程中的一段指令执行路径;或者说是CPU调度的最小单位。与进程相比,线程更加轻量级,可以提高CPU利用效率,充分发挥计算机的计算能力。在Java中,线程是指实现了java.lang.Thread类或者java.lang.Runnable接口的对象。 Thread类的基…

    多线程 2023年5月16日
    00
合作推广
合作推广
分享本页
返回顶部