浅谈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的多线程需要掌握线程并发运行的…

    多线程 2023年5月17日
    00
  • python并发和异步编程实例

    针对“python并发和异步编程实例”的完整攻略,本文将分为以下几个部分进行说明: 并发编程和异步编程的概念解释 并发编程实例演示 异步编程实例演示 总结和建议 1. 并发编程和异步编程的概念解释 在开始讲解并发编程和异步编程实例之前,我们需要先理解这两个概念。 并发编程是指同时执行多个任务,不一定要在同一时刻,但一段时间内它们是交替执行的。 异步编程是指仅…

    多线程 2023年5月16日
    00
  • Java多线程通信:交替打印ABAB实例

    Java多线程通信:交替打印ABAB实例是一个经典的多线程通信问题。在这个问题中,需要用到两个线程分别交替输出字符A和字符B,输出ABABAB…这样的交替序列。 下面,我将一步一步讲解如何实现这个问题。 问题描述 在这个问题中,我们需要使用两个线程分别交替打印字符A和字符B,输出ABABAB…这样的交替序列。 解决方案 为了实现这个问题,我们需要使用…

    多线程 2023年5月16日
    00
  • 分享J2EE的13种核心技术

    分享J2EE的13种核心技术攻略 1. 学习J2EE的目的 J2EE是Java 2企业版的缩写。它是一种Java开发平台,在开发大型企业应用时非常有用。J2EE平台提供了一个标准的框架,用于构建分布式和可扩展的企业应用程序。学习J2EE主要有以下目的: 理解J2EE平台的核心概念和架构 熟悉J2EE的编程模型和APIs 掌握J2EE开发的13种核心技术 2.…

    多线程 2023年5月17日
    00
  • java 多线程饥饿现象的问题解决方法

    Java多线程饥饿现象是指某个或某些线程因等待系统资源或其他线程的持续运行而无法执行的状态。这种情况下,影响线程执行的因素有很多,诸如资源的竞争、线程同步、死锁、负载不均等等。 为避免饥饿现象,在多线程编程过程中,必须采取措施从根源上解决这个问题。下面就讲解一些Java多线程饥饿现象的解决方法。 一、提高线程优先级 可以使用Java的Thread类提供的se…

    多线程 2023年5月17日
    00
  • java多线程并发中使用Lockers类将多线程共享资源锁定

    下面我将详细讲解Java多线程并发中使用Lockers类将多线程共享资源锁定的完整攻略。 1. 什么是Lockers类 Lockers类是Java中一个用于多线程并发控制的工具类,它提供了多个工具方法来方便锁定和释放共享资源。Lockers类是Java并发库中的一员,主要目的是提供比synchronized更加灵活和可控的锁定机制,同时也可以更好地支持公平锁…

    多线程 2023年5月17日
    00
  • Java并发编程之浅谈ReentrantLock

    下面我来详细讲解“Java并发编程之浅谈ReentrantLock”的完整攻略。 一、ReentrantLock概述 在Java中,我们可以用synchronized来实现线程同步。除此之外,还可以使用JDK提供的ReentrantLock类来实现线程同步和互斥。 ReentrantLock是一个可重入锁,它和synchronized相比,具有更加灵活的特性…

    多线程 2023年5月17日
    00
  • GoLang并发机制探究goroutine原理详细讲解

    GoLang并发机制探究goroutine原理详细讲解 什么是goroutine goroutine 是Go语言中的一种轻量级线程,能够在用户态(User Space)进行创建和销毁,不需要操作系统提供的线程管理和调度,因此比传统线程的创建、销毁和轮转开销更小,同时能够高效地利用多核CPU性能。 Go语言中的协程(goroutine)有着更加灵活的调度和更少…

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