2017-10-12 395 views
2

我正在使用C++ 11,并且我知道由于std::vector对bools的专门化,并发写入到std::vector<bool> someArray不是线程安全的。C++并发写入数组(而不是std :: vector)bools

我试图找出是否写入bool someArray[2048]有同样的问题:

  • 假设在someArray所有条目初始设置为
  • 假设我有一堆线程在someArray.的不同索引处写入实际上,这些线程只将假的的不同数组条目设置为true。
  • 假设我有一个阅读器线程,在某个时间点获取一个锁,触发内存围栏操作。

问:将读者看到所有的写操作someArray被收购的锁之前发生的?

谢谢!

+0

如果您需要线程安全性,请使用['std :: atomic_bool'](http://en.cppreference.com/w/cpp/atomic/atomic)并将其称为完成 – Mgetz

回答

3

您应该使用std::array<bool, 2048> someArray而不是bool someArray[2048];。如果您使用的是C++ 11版本,则需要尽可能多地更新代码。

std::array<bool, N>std::vector<bool>相同,因此在原始安全性方面没有问题。

至于你的实际问题:

将读者看到所有的写操作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(); 
    } 
} 

的细节将会根据底层硬件上有所不同,但在几乎所有情况下,func_a将是数量级比func_b更快,至少对于一个足够大的阵列大小(2048被选为一个例子,但它可能不代表实际的潜在性能差异)。这两个函数应该有相同的结果,但其中一个会比另一个快得多。

+0

谢谢Xirema!这是非常好的知道,我读过另一个SO帖子中类似的东西。另外,我实际上有一个'std :: atomic ',所有编写者都会在编写布尔数组之后递减。我想读者可以简单地阅读相同的原子变量来触发内存篱笆? –

+1

@AlinTomescu是的,这可能就足够了。但是,我建议您改为编写/获取适当的* Reader/Writer Lock *来保证正确的行为,否则使用Mutex + Semaphore(这是大多数读写器锁实现的方式)。 – Xirema

+0

@Xirema或者他们可以使用原子......不需要读写器锁定。 – Mgetz

0

首先,一般std :: vector不像您想象的那样是线程安全的。该担保已经规定here

解决您的问题:读者可能会在而不是获取锁后看到所有写入。这是由于这样的事实,即写入者可能永远不会执行释放操作,该操作是在写入和随后的读取之间建立发生关系之前所需的释放操作。用(非常)简单的术语来说:每个获取操作(例如互斥锁)都需要一个释放操作来同步。任何释放到某个可见元素之前完成的内存操作对于获取相同变量的任何线程都是可见的。另见Release-Acquire ordering

+0

要清楚,匹配锁定互斥锁时固有的获取操作是解锁操作中固有的释放操作。*表示相同的互斥锁*。即这两个操作通过对互斥体的地址进行rendezvousing来建立排序。释放/获取部分也可以在bools数组上使用atomic_store和atomic_load来建立,尽管避免竞争仍然需要某种形式的静音。 –

+0

谢谢!发布获取顺序实际上很好知道。但是,似乎'std :: vector '(当'T!= bool')确实支持并发写入:请参见“线程安全”部分[此处]中的第3项(http://en.cppreference.com/w/cpp/container),它表示_“显然,如果你使用'push_back',就像前面的SO中所描述的那样,”同一个容器中的不同元素可以被不同的线程同时修改,除了std :: vector 。帖子,这不是线程安全的。 –

0

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

相关问题