【问题标题】:Cross-thread visibility of changes to std::vector synchronized only with Win32 events仅与 Win32 事件同步的 std::vector 更改的跨线程可见性
【发布时间】:2019-09-13 12:22:26
【问题描述】:

假设我有一个std::vector<Item> 类成员变量,其中Item 是一些带有getter 和setter 的类。 它在一个线程 (#1) 中创建,但从另一个线程 (#2) 填充push_backs。最后它在线程#1 中被读取。对它的访问仅与 Windows event objects 同步。当向量被填满时,该事件被设置为信号状态。

在这种情况下,我应该注意跨线程可见性问题(获取陈旧值)吗?如果是,如何预防这些问题?

【问题讨论】:

  • #1 将如何对阵列进行操作?永远填充向量?还是会第二次添加项目?如果是这种情况,您还需要锁,事件是不够的。
  • @AndreaRossini #1 只是创建向量,然后运行 ​​#2 并在完成后(即事件发出信号)读取结果。
  • 如果我理解正确并且线程 #2 在发出事件信号后停止访问向量(或完全停止执行),则不需要进一步同步。
  • 也许你可以创建一个minimal reproducible example。由于您正在以序列化的方式做两件事,因此根本不知道为什么这是线程化的。
  • 事件更多是用于通知。如果我不使用您的事件但可以访问您的二进制文件,我想我可能会把事情搞砸。 Windows 中还有很多其他的同步对象:docs.microsoft.com/en-us/windows/win32/sync/…。 “关键部分”通常用于同步访问成员状态。

标签: c++ windows multithreading concurrency memory-model


【解决方案1】:

微软说等待事件对象就足够了。

根据MSDN

以下同步函数使用适当的屏障来确保内存排序:

  • 进入或离开临界区的函数
  • 向同步对象发送信号的函数
  • 等待函数
  • 联锁功能

这意味着如果线程 #1 看到事件对象发出信号的副作用,它必须看到线程 #2 修改向量的副作用。

【讨论】:

    【解决方案2】:

    如果线程#1 只在#2 发出信号后读取,而#2 在发出信号后不写入任何新成员,那么你就不用担心了。

    否则,如果读取和写入可能同时发生,则不会出现可见性问题,但会出现同步问题。 std::vector 不是线程安全的数据结构,因此如果两个线程同时访问它,它可能会被破坏。要么切换到不同的线程安全数据结构,要么用排他锁包围你的向量访问。对于标准解决方案,请查看 std::mutex。

    【讨论】:

      【解决方案3】:

      我建议尽可能使用标准同步原语。

      你需要锁定一次,直到向量被填满。例如。线程#2 将一直等到线程#1 完成。

      using Item = int;
      std::vector<Item> items;
      std::mutex mutex;
      
      // #1
      auto t1 = std::thread([&items, &mutex](){
        std::lock_guard<std::mutex> lock(mutex);
        // fill in items...
        // ...
      });
      
      // #2
      auto t2 = std::thread([&items, &mutex](){
        std::lock_guard<std::mutex> lock(mutex);
        // read...
      });
      
      // wait...
      t1.join();
      t2.join();
      

      【讨论】:

      • 您零保证t2 不会锁定mutex 之前 t1
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-03-05
      • 2012-06-30
      • 1970-01-01
      • 2020-12-08
      • 2012-10-12
      • 2013-06-02
      • 1970-01-01
      相关资源
      最近更新 更多