“线程屏障”同步模式上的 C++ 正确原子内存排序

2024-01-03

我需要在预定义数量的工作线程(通过应用程序配置静态已知)和预定义数量的控制平面线程之间正确同步对某些共享资源的访问。控制平面线程接收来自外部的请求,并基于该请求可能修改共享资源。工作线程只是运行一个无限循环,其中共享资源是只读的。为了以线程安全的方式做到这一点,并考虑到实际的应用程序用例(网络数据包处理、多数据平面线程和多控制平面线程),决定实现“线程屏障”类型的模式。以下是其实现方式的片段,假设应用程序配置为生成 2 个工作线程和 2 个控制平面线程:

std::atomic_bool barrier{};
std::atomic_uint32_t workers_at_barrier{};

// called by control-plane threads only!
void barrier_lock()
{
    // optimized spinlock implementation: rigtorp.se/spinlock/
    while (true)
    {
        if (!barrier.exchange(true, std::memory_order_acquire))
            break;

        while (barrier.load(std::memory_order_relaxed))
            __builtin_ia32_pause();
    }
    assert(barrier);

    // wait for ALL worker (data-plane) threads to arrive at the barrier!
    while (workers_at_barrier.load() != 2);
    assert(workers_at_barrier.load() == 2);
}

// called by control-plane threads only!
void barrier_unlock()
{
    assert(barrier && workers_at_barrier.load() == 2);
    barrier.store(false, std::memory_order_release);

    // wait for ALL workers to get out of the barrier!
    while (workers_at_barrier.load() != 0);
}

struct barrier_lock_guard
{
    barrier_lock_guard()
    {
        barrier_lock();
    }

    ~barrier_lock_guard()
    {
        barrier_unlock();
    }
};

// control-plane threads receive some requests and handles them here
void handle_stuff()
{
    // ... stuff

    {
        barrier_lock_guard blg;

        // barrier should be set and all workers (2 in this case) should be waiting at the barrier for its release
        assert(barrier && workers_at_barrier.load() == 2);

        // ... writes to shared resource
    }

    // ... stuff
}

// called by worker threads only!
void wait_at_barrier()
{
    // immediately return if barrier is not set
    if (!barrier.load(std::memory_order_acquire))
        return;
    
    ++workers_at_barrier;

    // block at the barrier until it gets released
    while (barrier.load(std::memory_order_acquire));

    --workers_at_barrier;
}

// function run by the worker threads
void workers_stuff()
{
    while (true)
    {
        wait_at_barrier();

        // ... reads from shared resource
    }
}

问题是断言assert(barrier && workers_at_barrier.load() == 2); in handle_stuff()正在受到打击。这种情况很少发生,所以一定是出了什么问题,我正在努力准确地了解问题出在哪里。很确定,虽然这与不正确的使用有关std::memory_order。任何 C++ 原子专业人士都可以向我指出确切的问题以及正确的解决方法是什么?提前致谢。


这不是内存排序问题,只是一场普通的竞赛。即使将所有内存顺序升级为顺序一致性后,我也可以重现它。这是我在 godbolt 上的版本 https://godbolt.org/z/GG7jvsqsn尽管我只能在本地重现故障(godbolt 仅在一个核心上运行)。

评论wait for ALL workers to get out of the barrier! in barrier_unlock似乎指出了问题。这个循环不强制another控制线程等待;其他线程可以立即占据屏障。

或者,观察值workers_at_barrier == 2 in barrier_lock()不能证明两个线程现在都在屏障处等待;他们可能在之前关闭时已经通过了它,但还没有抽出时间来减少原子计数器。

想象一下以下的事件顺序。我们有控制线程 C1、C2 和工作线程 W1、W2。 C1已经占据障碍物并且刚刚进入barrier_unlock()。 C2刚刚进入barrier_lock()。 W1和W2都在旋转while(barrier.load()) in wait_at_barrier(), and workers_at_barrier有价值2.

  1. C1: barrier.store(false)

  2. W1: barrier.load(): false,自旋循环退出

  3. C2: barrier.exchange(true):返回false。跳出循环。现在barrier == true.

  4. C2: assert(barrier)(通过)

  5. C2: workers_at_barrier.load(): 2. 的while循环立即退出。

  6. C2: assert(workers_at_barrier.load() == 2)(通过)

  7. C2 从返回barrier_lock()

  8. W1: --workers_at_barrier: 1

  9. C2 in handle_stuff(): Now barrier == true and workers_at_barrier == 1。断言失败。

我不确定临时的最佳解决方案。也许barrier应该有第三个“耗尽”状态,其中控制线程仍然拥有屏障,但工作人员可以离开它。只有在他们这样做之后,控制线程才会完全释放屏障。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

“线程屏障”同步模式上的 C++ 正确原子内存排序 的相关文章

随机推荐

  • 复制方法 IMP 以进行多个方法混合

    我设置了一个类 理想情况下将读取传入的任何类的方法 然后在运行时将它们全部映射到单个选择器 然后将它们转发到原始选择器 这现在确实有效 但我一次只能使用一种方法 问题似乎是 一旦我调整第一个方法 我用于捕获和转发该方法的 IMP 现在已与其
  • C# .ToString("X4") 在 Java 中等效

    在将 String 转换为 Unicode 时 我正在寻找一个真正相当于 Java 中的 C ToString X4 C Code char stringChars Notices ToCharArray List
  • 如何在log4j.xml中设置CATALINA_HOME?

    我需要这样的东西 参数名称 文件 值 CATALINA HOME logs log4j log 我看到很多类似的问题 但没有有效的解决方案 适用于 Tomcat 6 0 或更高版本catalina base 而不是catalina home
  • C# 查询 XML 文档

    再会 我正在尝试查询 XML 文档并有以下查询 XElement root XElement Load Data xml var entries root Descendants Where x gt x Name LocalName En
  • WPF 数据网格 [System.Windows.Data 错误:4]

    我有一个 WPF 应用程序DataGrid如下所示 数据网格 简化的
  • 如何从用户获取多行输入[重复]

    这个问题在这里已经有答案了 我想编写一个程序来获取多行输入并逐行使用它 为什么没有类似的功能raw input在 Python 3 中 input does not allow the user to put lines separated
  • Android 市场和 APK 文件名

    我将上传到 Android 市场的最终 apk 文件的名称重要吗 用户可以看到文件的名称吗 不 姓名 apk文件被忽略 用户将无法看到它 你可以命名 apk以您方便的任何方式 Android Market 只解析内部 apk文件并提取所需的
  • R随机森林不一致的预测

    我最近使用 R 中的 ranger 包构建了一个随机森林模型 但是 我注意到训练期间存储在 ranger 对象中的预测 可通过 model predictions 访问 与我在使用创建的模型的相同数据集 以下代码在 mtcars 数据集上重
  • 如何切换到旧版本的 ruby​​/rails 环境?

    我正在尝试与 Tekpub 在 Rails 上构建您自己的博客截屏视频保持一致 我仍然是一个 ruby 新手 问题是我安装了 Rails 3 而 Rob 使用旧版本 我的脑海中 版本 2 3 2 我知道如何获得该版本的 Railsgem i
  • 使用 PHP 格式化 JSON 数据

    我有以下代码
  • iPhone X 中的背景图像拉伸

    目前我们使用 1x 2x 和 3x 的图像资源 它将支持所有设备 但是 当我们在 iPhone X 上使用相同的图像资源时 3x 图像在 iPhone X 中看起来会被拉伸 因此 请让我知道如何支持具有相同图像资源的 iPhone X 设备
  • NSPredicate 用于过滤 NSString 类型的两个日期

    我有一些数据 如NSDictionary in an NSMutableArray 样本数据如下 将每一行视为NSDictionary整个表是一个NSMutableArray包含事件 我想在两个日期之间得出结果 所以我使用NSPredica
  • Firebase 分析事件没有显示价值

    我有一个游戏 我想在每次用户设置新的高分时发送事件 我检查当前分数是否 gt 之前的分数 如果是 我将新的高分发送到 firebase 代码 Bundle bundle new Bundle bundle putLong FirebaseA
  • 我使用 AntiXSS 但我仍然可以破解页面

    我不知道我这样做是否正确 我第一次构建一些东西来防止页面受到攻击 我将从最底层开始 我有财产 public string Description get set 用户可以通过tinyMCE设置其值 tinyMCE init mode tex
  • 从暂存器访问附加 SDK 模块

    我想使用暂存器测试我的附加代码的小片段 在这种情况下 我尝试像这样访问通知模块 const notify require sdk notifications notify My notification s options 正如预期的那样
  • NHibernate 与 IIS 7 的会话

    我有一个使用 NHibernate 的 ASP NET MVC 应用程序 并且该应用程序在通过 VS2008 虚拟 Web 服务器运行时运行良好 但是当我尝试通过本地 IIS 服务器运行该站点时 我不断收到此 NHibernate 错误 没
  • 提交 fetch() POST 请求后使用 fetch() GET 请求输出数据库数据,无需硬页面刷新

    我有一个使用 javascript 提交数据的表单fetch 使用 PHP 访问 MySQL 数据库的 API 在下面的代码中 当提交表单时 页面上会输出一条成功消息 并且由于fetch API 板模块本身最初是通过 添加到板 元素上的单击
  • 三角形不渲染[重复]

    这个问题在这里已经有答案了 我在渲染一个简单的三角形时遇到问题 下面的代码编译并运行 但没有任何三角形 只有黑色背景 GLuint VBO static void RenderSceneCB glClear sets the bitplan
  • Google Foobar:如何查找边缘情况并识别测试用例。蟒蛇[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 Problem 完美燃油喷射 拉姆达指挥官请求你帮助改进自动系统 LAMBCHOP 末日号的量子反物质燃料喷射系统 设备 这是您近距离
  • “线程屏障”同步模式上的 C++ 正确原子内存排序

    我需要在预定义数量的工作线程 通过应用程序配置静态已知 和预定义数量的控制平面线程之间正确同步对某些共享资源的访问 控制平面线程接收来自外部的请求 并基于该请求可能修改共享资源 工作线程只是运行一个无限循环 其中共享资源是只读的 为了以线程