【问题标题】:Is writing std::deque at different memory locations concurrently thread-safe?在不同的内存位置同时写入 std::deque 是线程安全的吗?
【发布时间】:2018-05-31 14:13:33
【问题描述】:

我有一个std::deque<std::pair<CustomObj, int>>,在启动并发块时大小不会改变。

并发块读取deque中的每个CustomObj并设置int

我可以保证双端队列不会改变大小,因此不会重新分配,并且每个线程只会访问双端队列的内存块,而不会访问其他线程的内存块。

是否会导致并发读写的未定义行为?我应该把写作和阅读放在一个互斥区吗?

【问题讨论】:

    标签: c++ multithreading c++11 openmp


    【解决方案1】:

    令我惊讶的是,在当前标准本身中实际上有一个非常明确的部分:

    (C++17, 26.2.2 容器数据竞争, 2)

    1. 尽管有 20.5.5.9 的规定,当同时修改同一容器中不同元素中包含的对象的内容(vector<bool> 除外)时,实现需要避免数据争用。

    您也可以毫无顾虑地调用以下访问器:

    1. 为了避免数据竞争 (20.5.5.9),实现应考虑以下功能 常量:begin, end, rbegin, rend, front, back, data, find, lower_bound, upper_bound, equal_range, at 并且,除了在关联或无序关联容器中,operator[]

    由于std::deque 也不例外,您可以同时调用任何这些函数来获取不同的元素并修改它们。只需确保将对容器本身的任何修改与您同时访问和修改元素的并行区域正确隔离。

    例如这是错误的:

    std::dequeue<...> dq;
    #pragma omp master
    {
        ...
        dq.emplace(...);
    }
    // no implicit barrier here,
    // use omp barrier or change to omp single instead of master
    #pragma omp for
    for (... i; ...)
        dq[i].second = compute(dq[i]);
    

    【讨论】:

    • 哦,伙计,您现在让我陷入了困境,不知道要标记哪个答案。感谢您的标准报价,这是一个很好的发现。
    • 我实际上想知道 Yakk 的答案中提到的一般规则如何适用于 std::vector&lt;bool&gt; 这导致我得到这个答案,因为对现有的评论太长了。
    • 只是为了确定,这个注释是否也在 C++11 上?
    • 是的,从 C++11 开始
    • 您还可以指出deque 在任一端插入时具有引用稳定性,因此如果可以在代码的同步部分中执行auto &amp;ref = dq[i];,那么ref 可以是与push_back()push_front() 等同时访问 - 不幸的是迭代器无效,因此在给定循环中,您必须事先存储每个指针或引用。
    【解决方案2】:

    只要您可以保证deque 的大小不会改变,并且只有一个线程会写入特定元素,那么是的,这是安全的。只有当您尝试修改 deque(更改它的大小)或者当您有多个线程读取和写入 deque 内的单个元素时,您才会遇到问题。

    你可以体验到的一件事叫做false sharing。这是具有多个线程正在使用的元素的单个高速缓存行的过程。由于线程写入会弄脏缓存行,因此需要重新同步整个内容,这会损害性能

    【讨论】:

    • 为防止虚假共享,请尝试使用同一线程访问相邻元素。通常,您可以使用标准的#pragma omp for
    • 感谢您提及祖尔坦。这正是我正在做的,所以我很放心。
    • @NathanOliver This answer on std::vector 声称“写入时没有并发阅读器”。它没有说他是指容器还是容器的元素。我仍然像您一样认为,鉴于我的上下文,写入和读取不同的内存位置是安全的。另外,我的并发块中没有push_back
    • @quimnuss 我指的是两者。只要您没有多个线程修改双端队列本身的状态(它的大小),并且只要您没有多个线程读取和修改 deque 内的单个元素,您将是安全的。跨度>
    【解决方案3】:

    所有标准容器的规则是:

    • 许多读者一个作家在整个容器和每个元素上。
    • 单个元素读取或修改(不添加/删除元素)也是对容器的读取操作。

    这只是适度太强了。您可以做少量违反上述规则的事情,而不会成为标准下的竞争条件。


    在标准中,这通常用容器上的const 方法来表述。读方法是const,写方法不是const。例外情况是非constbegin()end()data()(只返回迭代器的方法)算作const

    对于迭代和元素访问,它的措辞是迭代器失效。许多操作使迭代器失效,如果迭代器在使用时以无序方式失效,则它是一种竞争条件。

    作为一个例子,上面的经验法则说“不”但标准说“好的”:

    您可以存储地图和对地图中值的引用。您可以在另一个线程向映射添加键值对时编辑值。

    由于地图没有使迭代器失效,并且您没有触摸键,我相信没有竞争条件。

    【讨论】:

      猜你喜欢
      • 2014-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-05
      • 1970-01-01
      相关资源
      最近更新 更多