增加相同的全局变量时线程如何表现?

时间:2019-02-18 16:42:20

标签: c multithreading

请说明以下代码中尝试增加全局变量c的区别

我不清楚为什么在第一种情况下最大值为20而在第二种情况下最大值为10。

void * increment(void *vptr)
{   int i;
for (i = 0; i < 10; i++) {
c++ 
}
return(NULL);
}


void * increment(void *vptr)
{   int i, val;
for (i = 0; i < 10; i++) {
val = c;
c= val+1;   
}
return(NULL);
}

2 个答案:

答案 0 :(得分:0)

按照书面规定,运行上述两个功能中的任何一个的2个线程可能导致c20 。使用多线程时,无法保证两个线程同时或以相同的速度运行。一个线程可以在其他线程启动之前就完成,这取决于操作系统,硬件等。因此,除非您适当地防范race conditions,否则您不能依赖结果。

也就是说,两个功能执行的操作之间存在差异。具体来说,一个直接增加全局变量(可能是单个处理器指令),而另一个则将值复制到局部变量,然后将该局部值+1分配回全局变量。

对于您要描述的特定输出,操作序列如下所示:

情况1:

  1. c的首字母为:c = 0
  2. 线程1递增cc = 1
  3. 线程2递增cc = 2
  4. 线程1递增cc = 3
  5. 线程2递增cc = 4
  6. 线程1递增cc = 5
  7. 线程2递增cc = 6
  8. 线程1递增cc = 7
  9. 线程2递增cc = 8 。 。 。

情况2:

  1. c的首字母为:c = 0
  2. 线程1存储cc = 0, val = 0
  3. 线程2存储cc = 0, val = 0
  4. 线程1将val+1分配给cc = 1
  5. 线程2将val+1分配给cc = 1
  6. 线程1存储cc = 1, val = 1
  7. 线程2存储cc = 1, val = 1
  8. 线程1将val+1分配给cc = 2
  9. 线程2将val+1分配给cc = 2 。 。 。

但是,没有理由使两个运行第二个函数的线程也不能按以下顺序产生20

  1. c的首字母为:c = 0
  2. 线程1存储cc = 0, val = 0
  3. 线程1将val+1分配给cc = 1
  4. 线程2存储cc = 1, val = 1
  5. 线程2将val+1分配给cc = 2
  6. 线程1存储cc = 2, val = 2
  7. 线程1将val+1分配给cc = 3
  8. 线程2存储cc = 3, val = 3
  9. 线程2将val+1分配给cc = 4 。 。 。

答案 1 :(得分:0)

了解C从未执行过-将其转换(包括优化)为其他形式(通常是目标CPU的机器代码)。作为该转换的一部分,如果c不是volatile,则这两个代码版本可以变得完全相同,并且都可以优化为单个c += 10(没有任何循环,等等)。 )。

在机器代码中,通常执行add dword [c],10之类的指令涉及3个步骤-CPU读取旧值,然后将10加到旧值,然后存储新值。当有多个CPU时,这是一个问题-例如两个CPU都可以读取旧值,两个CPU都可以在旧值上加10,然后两个CPU都可以存储新值。为了防止这种情况,CPU采取某种方式来确保原子地执行(某些)指令。例如,对于lock add dword [c],10,“ lock”前缀告诉CPU在执行指令时确保没有其他修改值。在C源代码中没有特殊注释(例如原子变量)的情况下,C编译器没有理由生成这些(更昂贵的)指令,因此没有理由。在这种情况下(如果以c == 0开始,c中的最终值可能是10或20,这取决于每个CPU读取旧值并存储新值的确切时间。

如果cvolatile,或者不是,并且C编译器无法正确优化;然后可以将这两个版本都转换为执行c++;十次的代码(从字面上讲,像c++; c++; c++; ...或循环执行)。在这种情况下(如果以c == 0开始,c中的最终值可以是10到20之间的任何值(例如,可以是10、11、12、13,...,20),具体取决于每个CPU读取旧值并存储新值的确切时间。

具有单个CPU;在指令中间不能进行任务切换。这意味着,如果对C进行转换,使得每次c进行更新时,它都是通过一条指令(例如add dword [c],10inc dword [c])完成的,则不会出现竞争条件,并且(如果{{ 1}}最初),c == 0中的最终值为20;但是如果将C转换为每次更新c时都要用多条指令(例如c)完成,那么就会出现竞争条件(例如,在读取旧值之后但在新值之前进行任务切换)存储),其结果可能与“多个CPU”情况相同。