C++ 在不同内存位置同时写入std::deque是否线程安全?

C++ 在不同内存位置同时写入std::deque是否线程安全?,c++,multithreading,c++11,openmp,C++,Multithreading,C++11,Openmp,我有一个std::deque,它在启动并发块时大小不变 并发块读取deque的每个CustomObj,并设置int 我可以保证deque不会改变大小,因此不会重新分配,并且每个线程只访问deque的内存块,而不会访问其他线程的内存块 它是否会导致未定义的行为同时阅读和写作?我应该将写入和读取放在互斥区吗?只要您能保证deque的大小不会改变,并且只有一个线程会写入特定元素,那么是的,这是安全的。只有在尝试修改deque(更改其大小)或在deque中有多个线程读写单个元素时,才会出现问题 但有一件

我有一个
std::deque
,它在启动并发块时大小不变

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

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


它是否会导致未定义的行为同时阅读和写作?我应该将写入和读取放在互斥区吗?

只要您能保证
deque
的大小不会改变,并且只有一个线程会写入特定元素,那么是的,这是安全的。只有在尝试修改
deque
(更改其大小)或在
deque
中有多个线程读写单个元素时,才会出现问题


但有一件事你可以体验到,那就是。这是单个缓存线的进程,其中包含多个线程正在使用的元素。由于线程写入会弄脏缓存线,因此整个过程都需要重新同步,这将影响性能。对于所有标准容器,规则如下:

  • 在整个容器和每个元素上有多个读卡器或一个写卡器
  • 单个元素读取或修改(不添加/删除元素)也是容器上的读取操作
这只是有点太强了。你可以做一些违反上述规则的事情,而不是标准下的比赛条件


在标准中,这通常用容器上的
const
方法来表示。读取方法是
const
,写入方法不是
const
。例外情况是非常量的
begin()
end()
data()
(只返回迭代器的方法)计为
const

对于迭代和元素访问,它用迭代器无效来表示。许多操作使迭代器失效,如果迭代器在使用过程中以非顺序方式失效,则这是一个竞争条件

例如,上面的经验法则是“否”,但标准是“ok”:

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


由于没有迭代器会因映射而失效,而且您没有触碰键,因此我认为不存在竞争条件。

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

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

  • 尽管有20.5.5.9的规定,但当同一容器中不同元素(向量
    除外)中包含的对象的内容同时被修改时,需要实现以避免数据竞争
  • 此外,您还可以无需担心地调用以下访问器:

    <> LI>出于避免数据竞争的目的(20.5.5.9),实现应考虑以下功能: 常量:
    begin、end、rbegin、rend、front、back、data、find、lower\u bound、upper\u bound、equal\u range、at
    并且,除了在关联或无序关联容器中,
    运算符[]
    因为
    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]);
    
    std::退出dq队列;
    #pragma-omp-master
    {
    ...
    dq.侵位(…);
    }
    //这里没有隐含的障碍,
    //使用omp屏障或更改为omp single而不是master
    #pragma omp for
    对于(…i;…)
    dq[i].second=compute(dq[i]);
    
    为防止错误共享,请尝试使用同一线程访问相邻元素。通常,您可以使用标准的
    #pragma omp for
    。感谢您提到Zultan。这正是我正在做的,所以我很放心。@NathanOliver在
    std::vector
    上声称“在编写时没有并发读卡器”。它没有说他是指容器还是容器的一个元素。我仍然认为,像你一样,在我的上下文中,写和读不同的内存位置是安全的。此外,在我的并发块中没有
    推回
    。@quimnuss我指的是两者。只要你没有多个线程来修改deque本身的状态(它的大小),只要你没有多个线程来读取和修改
    deque
    中的单个元素,你就安全了。哦,伙计,你现在让我很为难知道该标记哪个答案。谢谢你的标准引用,这是一个极好的发现。我真的很想知道雅克的答案中提到的关于
    std::vector
    的一般规则是如何适用的,这让我找到了这个答案,因为它太长了,无法对现有的答案进行评论。可以肯定的是,这个注释也在C++11上吗?是的,由于C++11,您还可以指出,
    deque
    在两端插入时具有引用稳定性,因此如果可以执行
    auto&ref=dq[i]
    在代码的同步部分,然后
    ref
    可以与
    push\u back()
    push\u front()
    等同时访问。不幸的是,迭代器无效,因此在给定的循环中,您必须事先存储每个指针或引用。