请说明以下代码中尝试增加全局变量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);
}
答案 0 :(得分:0)
按照书面规定,运行上述两个功能中的任何一个的2个线程可能导致c
为20
。使用多线程时,无法保证两个线程同时或以相同的速度运行。一个线程可以在其他线程启动之前就完成,这取决于操作系统,硬件等。因此,除非您适当地防范race conditions,否则您不能依赖结果。
也就是说,两个功能执行的操作之间存在差异。具体来说,一个直接增加全局变量(可能是单个处理器指令),而另一个则将值复制到局部变量,然后将该局部值+1分配回全局变量。
对于您要描述的特定输出,操作序列如下所示:
情况1:
c
的首字母为:c = 0
c
:c = 1
c
:c = 2
c
:c = 3
c
:c = 4
c
:c = 5
c
:c = 6
c
:c = 7
c
:c = 8
。 。 。情况2:
c
的首字母为:c = 0
c
:c = 0, val = 0
c
:c = 0, val = 0
val+1
分配给c
:c = 1
val+1
分配给c
:c = 1
c
:c = 1, val = 1
c
:c = 1, val = 1
val+1
分配给c
:c = 2
val+1
分配给c
:c = 2
。 。 。但是,没有理由使两个运行第二个函数的线程也不能按以下顺序产生20
:
c
的首字母为:c = 0
c
:c = 0, val = 0
val+1
分配给c
:c = 1
c
:c = 1, val = 1
val+1
分配给c
:c = 2
c
:c = 2, val = 2
val+1
分配给c
:c = 3
c
:c = 3, val = 3
val+1
分配给c
:c = 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读取旧值并存储新值的确切时间。
如果c
是volatile
,或者不是,并且C编译器无法正确优化;然后可以将这两个版本都转换为执行c++;
十次的代码(从字面上讲,像c++; c++; c++; ...
或循环执行)。在这种情况下(如果以c == 0
开始,c
中的最终值可以是10到20之间的任何值(例如,可以是10、11、12、13,...,20),具体取决于每个CPU读取旧值并存储新值的确切时间。
具有单个CPU;在指令中间不能进行任务切换。这意味着,如果对C进行转换,使得每次c
进行更新时,它都是通过一条指令(例如add dword [c],10
或inc dword [c]
)完成的,则不会出现竞争条件,并且(如果{{ 1}}最初),c == 0
中的最终值为20;但是如果将C转换为每次更新c
时都要用多条指令(例如c
)完成,那么就会出现竞争条件(例如,在读取旧值之后但在新值之前进行任务切换)存储),其结果可能与“多个CPU”情况相同。