C++ C++;对bools数组(非std::vector)的并发写入

C++ C++;对bools数组(非std::vector)的并发写入,c++,arrays,multithreading,c++11,vector,C++,Arrays,Multithreading,C++11,Vector,我使用的是C++11,我知道并发写入std::vector someArray不是线程安全的,因为std::vector专门用于bools 我试图找出写入boolsomearray[2048]的数据是否存在相同的问题: 假设someArray中的所有条目最初都设置为false 假设我有一组线程在someArray中以不同的索引写入。事实上,这些线程只将不同的数组项从false设置为true。 假设我有一个读线程,它在某个点获得了一个锁,触发了一个内存围栏操作 Q:读卡器会看到在获取锁之前发生

我使用的是C++11,我知道并发写入
std::vector someArray
不是线程安全的,因为
std::vector
专门用于bools

我试图找出写入
boolsomearray[2048]
的数据是否存在相同的问题:

  • 假设
    someArray
    中的所有条目最初都设置为false
  • 假设我有一组线程在
    someArray中以不同的索引写入。
    事实上,这些线程只将不同的数组项从false设置为true。
  • 假设我有一个读线程,它在某个点获得了一个锁,触发了一个内存围栏操作
Q:读卡器会看到在获取锁之前发生的对
someArray
的所有写入吗


谢谢

首先,一般的std::vector并不像您想象的那样是线程安全的。担保已经说明


回答您的问题:在获取锁后,读者可能看不到所有写操作。这是因为写入程序可能从未执行过在写入和后续读取之间建立“先发生后读取”关系所需的释放操作。(非常)简单地说:每个获取操作(如互斥锁)都需要一个释放操作来同步。在释放到某个veriable之前所做的每一个内存操作对于获取相同变量的任何线程都是可见的。另请参见。

您应该使用
std::array someArray
,而不是
bool someArray[2048]。如果您在C++11环境中,您将尽可能地使代码现代化

std::array
不像
std::vector
那样专门化,因此不需要考虑原始安全性

至于你的实际问题:

读卡器会看到在获取锁之前发生的对
someArray
的所有写入吗

仅当数组的写入程序也与锁进行交互时,可以在完成写入时释放锁,也可以更新与读卡器同步的锁关联的值。如果写入程序从未与锁交互,则读取器将检索的数据未定义

您还需要记住一件事:虽然让多个线程写入同一个数组并不不安全,如果它们都写入到唯一的内存地址,那么与缓存的交互可能会大大降低写入速度。例如:

void func_a() {
    std::array<bool, 2048> someArray{};
    for(int i = 0; i < 8; i++) {
        std::thread writer([i, &someArray]{
            for(size_t index = i * 256; index < (i+1) * 256; index++) 
                someArray[index] = true;
            //Some kind of synchronization mechanism you need to work out yourself
        });
        writer.detach();
    }
}

void func_b() {
    std::array<bool, 2048> someArray{};
    for(int i = 0; i < 8; i++) {
        std::thread writer([i, &someArray]{
            for(size_t index = i; index < 2048; index += 8) 
                someArray[index] = true;
            //Some kind of synchronization mechanism you need to work out yourself
        });
        writer.detach();
    }
}
void func_a(){
std::array someArray{};
对于(int i=0;i<8;i++){
std::线程编写器([i,&someArray]{
对于(大小索引=i*256;索引<(i+1)*256;索引++)
someArray[index]=true;
//你自己需要某种同步机制
});
writer.detach();
}
}
void func_b(){
std::array someArray{};
对于(int i=0;i<8;i++){
std::线程编写器([i,&someArray]{
对于(尺寸指数=i;指数<2048;指数+=8)
someArray[index]=true;
//你自己需要某种同步机制
});
writer.detach();
}
}

细节将因底层硬件而异,但在几乎所有情况下,
func_a
将比
func_b
快几个数量级,至少对于足够大的阵列大小而言(选择2048作为示例,但它可能无法代表实际的底层性能差异). 两个函数应该具有相同的结果,但其中一个函数的速度要比另一个函数快得多。

需要注意的一点是,在int32大小的变量(如bool)上的所有操作(获取和存储)都是原子的(对于x86或x64体系结构适用)。因此,如果您将数组声明为volatile(每个线程可能都有一个缓存的数组值,这是必需的),那么(通过多个线程)修改数组时不应该有任何问题。

要清楚,必须与锁定互斥锁固有的获取操作相匹配的是解锁同一互斥锁固有的释放操作。即,这两个操作通过在互斥体的地址上会合来建立顺序。发布/获取部分也可以在布尔数组中使用原子存储和原子加载来建立,不过避免竞争仍然需要某种形式的静音。谢谢!发布获取命令实际上是很好的了解。然而,似乎
std::vector
(当
T!=bool
时)确实支持并发写入:请参阅“线程安全”部分的第3项,其中指出“同一容器中的不同元素可以由不同线程并发修改,std::vector的元素除外。”显然,如果
推回
,如前一篇SO帖子所述,这不是线程安全的。如果您需要线程安全,请使用并称之为doneThank you Xirema!很高兴知道这一点,我在另一篇SO帖子中读到了类似的内容。另外,我实际上有一个
std::atomic
,所有写入程序在写入bool数组后都会减少。我想读者可以简单地读取同一个原子变量来触发内存栅栏?@AlinTomescu是的,这可能就足够了。但是,我建议您改为编写/获取适当的读写器锁以保证正确的行为,或者使用互斥+信号量(这是大多数读写器锁的实现方式)。@Xirema或者他们可以使用原子。。。它不需要读写器锁。@Mgetz原子是如何(经常)实现信号量的,它是R/W锁的一个组件。我试图将OP从低级同步转向高级同步,因为低级同步对象