表达式“a == 1?1:0”与比较加三元运算符表达式原子?

时间:2013-03-05 18:55:26

标签: java c++ pthreads atomic

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

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

  int a;
}

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

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

9 个答案:

答案 0 :(得分:8)

不,对于C ++和Java。

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

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

答案 1 :(得分:7)

return a==1 ? 1 : 0;

是一种简单的写作方式

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

我没有看到任何更新的代码。但我认为你可以搞清楚。

答案 2 :(得分:5)

无论是否存在写入,在C ++中读取非原子类型的值都是原子操作。如果没有写入,那么你可能不在乎它是否是原子的;如果某个其他线程可能正在修改该值,那么你肯定会关心。

答案 3 :(得分:2)

正确的放置方式很简单:不! (适用于Java和C ++)

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

您发布的代码中没有修改任何内容,仅测试变量。因此,代码通常会导致访问该存储器位置的单个TEST(或类似)指令,即,本质上是原子的。该指令将读取一个缓存行,并且在相应的loaction中将有一个明确定义的值,无论它是什么。

然而,这是一个inc inc / acc,,不是你可以依赖的东西。

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

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

答案 4 :(得分:1)

不,它不是原子的(一般情况下),虽然它可以在某些体系结构中(例如,在C ++中,如果整数是对齐的,那么除非你强制它不是这样)。

考虑这三个主题:

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

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

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

答案 5 :(得分:0)

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

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

这只是语法糖。

答案 6 :(得分:0)

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

   a == 1

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

bool flag = somefoo.test();

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

答案 7 :(得分:0)

这里有很多好的答案,但没有一个人提到Java需要将a标记为volatile

如果没有采用其他同步方法,则尤其很重要,但其他线程可以更新a。否则,您可能正在阅读旧值a

答案 8 :(得分: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()之间)

要修复此类竞赛,您需要使用编译器和/或内存障碍(如果您不是专家 - 只需使用锁定。)