2

快速提问?这条线在 C++ 和 Java 中是原子的吗?

class foo {
  bool test() {
    // Is this line atomic?
    return a==1 ? 1 : 0;
  }

  int a;
}

如果有多个线程访问该行,我们最终可以先检查 a==1,然后更新 a,然后返回,对吗?

补充:我没有完成课程,当然还有其他部分更新了...

4

9 回答 9

8

不,对于 C++ 和 Java。

在 Java 中,您需要以相同的方式创建您的方法synchronized并保护其他用途a。确保在所有情况下都在同一个对象上进行同步。

在 C++ 中,您需要使用std::mutexto protect a,可能std::lock_guard用于确保在函数结束时正确解锁互斥锁。

于 2013-03-05T18:59:00.993 回答
7
return a==1 ? 1 : 0;

是一种简单的写作方式

if(a == 1)
    return 1;
else
    return 0;

我没有看到任何用于更新 a 的代码。但我想你可以弄清楚。

于 2013-03-05T18:58:24.410 回答
5

不管有没有写,在C++中读取一个非原子类型的值都不是原子操作。如果没有写入,那么您可能不在乎它是否是原子的;如果其他一些线程可能正在修改该值,那么您当然会关心。

于 2013-03-05T19:04:05.183 回答
2

正确的说法很简单:不!(适用于 Java 和 C++)

一个不太正确但更实际的答案是:从技术上讲,这不是原子的,但在大多数主流架构上,至少对于 C++ 来说是这样。

您发布的代码中没有任何内容被修改,该变量仅经过测试。因此,代码通常会导致访问该内存位置的单个TEST(或类似)指令,顺便说一下,是原子的。该指令将读取一个高速缓存行,并且在相应的位置中将有一个明确定义的值,无论它可能是什么。

但是,这是偶然/偶然的,不是您可以依赖的。

当单个其他线程写入该值时,它通常甚至会起作用 - 再次,偶然地/偶然地。为此,CPU 获取一个高速缓存行,覆盖高速缓存行内相应地址的位置,并将整个高速缓存行写回 RAM。当您测试变量时,您会获取一个包含旧值或新值(两者之间没有任何内容)的缓存行。没有任何形式的保证,但您仍然可以认为这是“原子的”。

当多个线程同时修改该变量时(不是问题的一部分),情况要复杂得多。为了使其正常工作,您需要使用 C++11 中的某些东西<atomic>,或者使用原子内在函数或类似的东西。否则,非常不清楚会发生什么,以及操作的结果可能是什么——一个线程可能会读取该值,将其递增并将其写回,但另一个线程可能会在修改后的值被写回之前读取原始值。
在所有当前平台上,这或多或少肯定会以糟糕的方式结束。

于 2013-03-05T19:01:10.540 回答
1

不,它不是原子的(通常),尽管它可以在某些体系结构中(例如,在 C++ 中,在 intel 中,如果整数是对齐的,除非你强迫它不对齐)。

考虑这三个线程:

// thread one:                // thread two:             //thread three
while (true)                  while (true)               while (a) ;
   a = 0xFFFF0000;               a = 0x0000FFFF;

如果写入a不是原子的(例如,intel ifa是未对齐的,并且为了讨论,两个连续高速缓存行中的每一行都有 16 位)。现在,虽然第三个线程似乎永远无法退出循环(两个可能的值a都是非零),但事实是分配不是原子的,线程二可以将高 16 位更新为 0,并且线程三可以在线程二获得时间完成更新之前将低 16 位读取为 0,然后退出循环。

整个条件与问题无关,因为返回值是线程本地的。

于 2013-03-05T19:28:18.507 回答
0

您的问题可以改写为:是声明:

   a == 1

原子与否?不,它不是原子的,您应该使用 std::atomic 作为 a 或在某种锁定下检查该条件。在这种情况下,如果整个三元运算符原子与否无关紧要,因为它不会改变任何东西。如果你的意思是在你的问题中如果在这段代码中:

bool flag = somefoo.test();

标志与 a == 1 一致,它肯定不会,并且如果您的问题中的整个三元运算符是原子的,则无关紧要。

于 2013-03-05T19:11:38.473 回答
0

不,它仍然是一个测试,然后是一组,然后是一个返回。

是的,多线程将是一个问题。

这只是语法糖。

于 2013-03-05T18:57:08.560 回答
0

考虑以下代码:

bool done = false;

void Thread1() {
  while (!done) {
    do_something_useful_in_a_loop_1();
  } 
  do_thread1_cleanup();
}

void Thread2() {
  do_something_useful_2();
  done = true;
  do_thread2_cleanup();
}

这两个线程之间的同步是使用一个布尔变量 done 完成的。这是同步两个线程的错误方法。

在 x86 上,最大的问题是编译时优化。

编译器可以将 do_something_useful_2() 的部分代码移到“done = true”下方。编译器可以将 do_thread2_cleanup() 的部分代码移到“done = true”之上。如果 do_something_useful_in_a_loop_1() 没有修改“done”,编译器可能会通过以下方式重写 Thread1:

  if (!done) {
    while(true) {
      do_something_useful_in_a_loop_1();
    } 
  }
  do_thread1_cleanup();

所以 Thread1 永远不会退出。

在 x86 以外的架构上,缓存效果或乱序指令执行可能会导致其他微妙的问题。

大多数种族检测器都会检测到这样的种族。

此外,大多数动态竞争检测器将报告旨在与此 bool 同步的内存访问的数据竞争

(即在 do_something_useful_2() 和 do_thread1_cleanup() 之间)

要解决此类竞争,您需要使用编译器和/或内存屏障(如果您不是专家 - 只需使用锁)。

于 2013-05-09T03:42:35.257 回答
0

这里有很多好的答案,但没有一个提到在 Java 中需要标记avolatile.

如果没有采用其他同步方法,这一点尤其a重要,但其他线程可以更新. 否则,您可能正在读取a.

于 2013-03-08T17:00:47.837 回答