c++使用IntAtomicGet的原因

Reason for C++ IntAtomicGet, GotW

本文关键字:IntAtomicGet 使用 c++      更新时间:2023-10-16

在GotW的第45篇文章中,Herb陈述如下:

void String::AboutToModify(
  size_t n,
  bool   bMarkUnshareable /* = false */
) {
  if( data_->refs > 1 && data_->refs != Unshareable ) {
    /* ... etc. ... */

这个if条件不是线程安全的。首先,即使计算"data_->refs> 1"也可能不是原子性的;如果是这样,如果线程1试图计算"data__ ->refs> 1",而线程2正在更新refs的值,那么从data__ ->refs读取的值可能是任何值——1、2,甚至既不是原始值也不是新值。

此外,他指出data_->ref可能在与1比较和与Unshareable比较之间被修改。

再往下,我们找到了一个解决方案:

void String::AboutToModify(
  size_t n,
  bool   bMarkUnshareable /* = false */
) {
  int refs = IntAtomicGet( data_->refs );
  if( refs > 1 && refs != Unshareable ) {
    /* ... etc. ...*/

现在,我明白了两个比较使用了相同的refs,解决了问题2。但是为什么要使用IntAtomicGet?我在这个主题的搜索中没有找到任何东西——所有的原子操作都集中在读、修改、写操作上,这里我们只有一个读操作。所以我们能不能…

int refs = data_->refs;

…这应该是最后的一个指令?

不同的平台对读/写操作的原子性做出了不同的承诺。例如,x86保证读取双字(4 bytes)将是一个原子操作。但是,您不能假设这对任何体系结构都是正确的,而且很可能不是。

如果您计划将代码移植到不同的平台,这样的假设可能会给您带来麻烦,并导致代码中出现奇怪的竞争条件。因此,最好保护自己,并显式地使读/写操作原子化。

当另一个线程写入共享内存(data_->refs)时,从它读取数据是数据竞争的定义。

当我们非原子地从data_->refs读取而另一个线程同时试图写入它时会发生什么?

假设线程A正在执行++data_->refs(写),而线程B正在执行int x = data_->refs(读)。想象一下,线程B从data_->refs读取前几个字节,线程A在线程B完成读取之前将其值写入data_->refs。然后线程B读取data_->refs处的剩余字节。

您既不会得到原始值,也不会得到新值;你会得到一个完全不同的价值!这个场景只是为了说明:

是什么意思

[…从data__ ->refs中读取的值可以是任何值——1、2或即使不是原值也不是新值

原子操作的目的是确保操作是不可分割的:它要么被观察到已完成,要么被观察到未完成。因此,我们使用原子读操作来确保我们在data_->refs更新之前或之后获得它的值(这取决于线程计时)。