[CSDN久违了!!]求助哈,Windows的多线程中,对bool变量是否需要同步保护?请高手给出准确的理由和说法,谢谢了哦.....

时间:2021-08-19 18:09:04
为什么在多线程中bool变量不需要同步保护,但BOOL就需要同步保护?

请高手多提意见.

能否汇编代码的角度给俺一个明确的说法,多谢!

如果分不够,俺再加!!

另外,庆祝CSDN又开放了,恭喜Coder们.哈..........

31 个解决方案

#1


谁说的

#2


这不是从汇编角度能解释的问题。
是否需要同步要视具体应用情况而定。

#3


谁说不需要

#4


要的呢,需要使用volatile作为修饰符。假如你定义一个全局变量bool isExist控制线程是否退出,那么这个变量这么声明:
static volatile bool isExist;
线程函数类似于:
DWORD ThreadProc(LPVOID)
{
   isExist=true;
   while(isExist)
   {
      //do something
   }
}
如果不使用volatile,只要while循环不去修改isExist的值,那么编译器会优化全局变量的处理,对于那个线程而言,isExist恒为true;这个代码等价于:
DWORD ThreadProc(LPVOID)
{
   isExist=true;
   while(true)
   {
      //do something
   }
}
这样一来,别的线程无法控制当前线程的退出。
使用volatile表示变量会不经意地被修改,它告诉编译器不要对此变量作优化。因此isExist将以真实的值呈现出来。

#5


楼上的几位兄弟,你们能否先问一下身边的牛人们,再跟我解释?

一位IT业16年的老前辈跟我说过,bool变量不需要同步保护,BOOL变量需要同步保护,是与操作系统的操作粒度有关.windows的操作粒度为16个字节(再往后,就不记得他说的其它的话了).

2楼和4楼的回答明显不负责任.而5楼的兄弟的回答好像是从另外一个角度给了解释.....

至于3楼的回答,前提就是需要使用同步保护的时候,来讨论这两种变量的不同.不明白?

还是想寻求高手的解答,如何从汇编的角度来解释为什么bool不需要同步保护???

盼高手!!!!

#6


“楼上的几位兄弟,你们能否先问一下身边的牛人们,再跟我解释? ”
“2楼和4楼的回答明显不负责任”

既然身边有牛人,怎么不直接请教,何必来这里问。

#7


16年可以称为前辈,但前辈说的话不一定就是正确的,而且即使是正确的说法,也是有其适用范围的,不要断章取义。
如果按编程的时间来说,我是从1985年开始学习编程的,可能比那位前辈还前辈,我身边很难找到时间更长的“牛人”来问。不过我说的话也不见得是对的,需要你自己来思考和验证。

前面已经说了,是否需要同步要视具体应用情况而定。
单任务时代没有同步的概念;单处理器环境如果是原子指令访问变量,可以确保在修改过程中不会运行其它线程,这个可以从汇编角度来解释;汇编指令是在一个处理器中执行的,多处理器环境下已经不能再用汇编来考虑同步问题了。
bool变量只有一位有效,只有0和1两种状态,如果只是要给它赋值而不考虑其值对其它线程的影响,那么可以不用同步。但很多时候程序并非这么简单。
例如:假设你想要用一个bool变量作为线程互斥的开关,同一时间只允许一个线程执行某段代码。通常会想到的做法是:在开始时判断变量的值,当为0时等待重试;当为1时,先把变量设置为0,在执行完这段代码后再把变量设置为1。这种做法就隐藏着问题,因为在判断为1之后,设置为0之前,可能其它线程也会执行到判断变量的地方,由此导致多个线程同时执行了这段代码。
再举个复杂点的例子:当同时执行的多个任务之间存在复杂的逻辑关系时,可能多个线程需要使用同一个bool变量进行逻辑运算(与、或、非、异或等)时,处理器需要先从内存中读出数据,经过运算后再把结果保存回内存中,在运算过程中,其它处理器中运行的线程也可能要对此变量进行类型的运算,如果没有考虑同步,就会产生错误的结果。

#8


呵呵,可能我上面说的话偏颇.

但我只想知道如何从汇编的角度考虑为什么需要对变量进行同步保护.

为什么这么多高手看不懂我题目里的意思呢?呵呵.....

如果cnzdgs前辈有时间,再麻烦您从汇编的角度来给我解释一下为什么变量需要进行同步保护.多谢了!!!

#9


TO rageliu:

难道你认为我是*么?即使我能再次去请教他,知识不嫌多,我也可以到CSDN上发帖子来请教其它高手吧?
而你回的"既然身边有牛人,怎么不直接请教,何必来这里问。", 是在用讨论问题的态度来跟我交流么?我说那些兄弟(可能也包括你)回答帖子不负责任,我说错了么?有用"谁说的"这三个字就算是回答问题的么?

如果有兴趣讨论,请摆正你的态度,好么?

感激不尽!!

#10


偶是新手    关注ing

#11


偶不是高手,偶也想问:谁说的?为什么?

#12


你的言下之意,是已经认定你的说法是对的,只是要解释而已。

我说“谁说的”,并没有其他意思,只是表示你的说法我不同意,看看你说这话是否有你的运用环境而已

楼主用不着激动,如果你认为我语气不对,当我没说,不用“难道你认为我是*么?”这样吧

#13


哈,看来,星星多不多和水平高不高,是没有必然关系的.

还是盼高人指点.

#14


该回复于2008-08-19 17:13:21被版主删除

#15


g_bValue = false;
0041151E  mov         byte ptr [g_bValue (41905Ch)],0 
g_bValue ++;
00411525  mov         al,byte ptr [g_bValue (41905Ch)] 
0041152A  mov         byte ptr [ebp-0C5h],al 
00411530  mov         byte ptr [g_bValue (41905Ch)],1 

g_nValue = FALSE;
0041157E  mov         dword ptr [g_nValue (419060h)],0 
g_nValue ++;
00411588  mov         eax,dword ptr [g_nValue (419060h)] 
0041158D  add         eax,1 
00411590  mov         dword ptr [g_nValue (419060h)],eax 

实际上不bool在这里就是BYTE,BOOL是DWORD,仅仅这个差别而已,如果你一定要对 bool 做加法那么在多线程中还是小心为妙
对于加减法可以使用InterlockedIncrement和InterlockedDecrement来在多线程中保证原子操作

#16


当然如果你要做的只是赋值而已,大可不必折腾

#17


当然,如果你要做的仅仅是一个指令就可以搞定的赋值,大可不必折腾

#18


..........

#19


多谢qrlvls的回答,请问,从汇编的角度,如何看一个变量需要被同步保护.

#20


引用 19 楼 yysbest 的回复:
多谢qrlvls的回答,请问,从汇编的角度,如何看一个变量需要被同步保护.


高级语言的一句话可能对应汇编的好几句,假设我们对一个变量等的操作,对应汇编有1 2 3这三个步骤。可能我们在线程1里面执行了1 2这两步。此时发生了线程切换,去到了线程2,刚好线程2里面修改了变量。然后切换回线程1,线程1接着3开始执行,这时候变量其实已经被线程2修改了,可是线程1并不知道,这就出现了问题。

上面我们说“对应汇编有1 2 3这三个步骤”,而如果对变量的操作就一步,直接完成,很多问题就可以避免。

说的比较乱,只是举个例好说明,好多地方是否需要同步,还是看具体环境

#21


就拿InterlockedIncrement和InterlockedDecrement这两个函数说明好了:
互锁族函数保证增加这个变量值的时候不会被别的线程修改寄存器,考虑如下伪码: 

mov eax, A 
inc eax 
mov A, eax 

如果这段代码存在于2个任务,task1和task2,然后发生如下调度过程 

;task1执行,设A = 0 
mov eax, A 
inc eax 
;系统切换到task2,保存task1的上下文(包括eax的值,eax = 1) 
mov eax, A 
inc eax 
mov A, eax 
;系统切换到task1,保存task2的上下文,恢复的task1的上下文(注意:eax = 1) 
mov A,eax 

结果A = 1 

然而,如果task1和task2顺序执行: 
;task1执行,设A = 0 
mov eax, A 
inc eax 
mov A, eax 
;系统切换到task2 
mov eax, A 
inc eax 
mov A, eax 

结果A = 2 


#22


引用 7 楼 cnzdgs 的回复:
16年可以称为前辈,但前辈说的话不一定就是正确的,而且即使是正确的说法,也是有其适用范围的,不要断章取义。 
如果按编程的时间来说,我是从1985年开始学习编程的,可能比那位前辈还前辈,我身边很难找到时间更长的“牛人”来问。不过我说的话也不见得是对的,需要你自己来思考和验证。 

前面已经说了,是否需要同步要视具体应用情况而定。 
单任务时代没有同步的概念;单处理器环境如果是原子指令访问变量,可以确保在修…

呵呵,那位大侠开始搞编程的时候,我才三岁,还在托儿所呢!

#23


cnzdgs编程之后的第2年我才出生.

对于这个问题来说,如果只是一个原子操作,不用保护,和操作粒度有啥绝对关系?

#24


尝试解释一下:
cpu有的是32位,有的是64位。
而BOOL其实是一个DWORD,在32位就是4个字节。
一个汇编指令是一个原子操作,要么成功,要么不成功。
因此在执行一个指令的时候,如果操作数是内存中的4个字节的话,此时别的线程、cpu是无法在此时占用总线的。因此不用加锁。
因此反汇编32位系统上的Interlocked***函数的时候,很多时候你不会看到汇编指令的lock前缀。

但增加该前缀总是安全的。

#25


引用 20 楼 rageliu 的回复:
引用 19 楼 yysbest 的回复:
多谢qrlvls的回答,请问,从汇编的角度,如何看一个变量需要被同步保护. 
 

高级语言的一句话可能对应汇编的好几句,假设我们对一个变量等的操作,对应汇编有1 2 3这三个步骤。可能我们在线程1里面执行了1 2这两步。此时发生了线程切换,去到了线程2,刚好线程2里面修改了变量。然后切换回线程1,线程1接着3开始执行,这时候变量其实已经被线程2修改了,可是线程1并不知道,这就出现了问题。 

上…


呵呵,多谢兄弟的解释,有点明确了..你说的情况是存在的,所以在这个时候,必须使用同步保护.

那么,对于BOOL和bool,在这种情形下,有什么不同么?是不是对BOOL的赋值需要3步,而对bool变量的赋值只需要1步,所以就不需要同步保护了,是不是这个意思?

#26


引用 24 楼 hxfjb 的回复:
尝试解释一下: 
cpu有的是32位,有的是64位。 
而BOOL其实是一个DWORD,在32位就是4个字节。 
一个汇编指令是一个原子操作,要么成功,要么不成功。 
因此在执行一个指令的时候,如果操作数是内存中的4个字节的话,此时别的线程、cpu是无法在此时占用总线的。因此不用加锁。 
因此反汇编32位系统上的Interlocked***函数的时候,很多时候你不会看到汇编指令的lock前缀。 

但增加该前缀总是安全的。


呵呵,这个应该有些道理的

我一直在找官方的正归解释,但现在还没找到...

#27


引用 21 楼 paerxiushi 的回复:
就拿InterlockedIncrement和InterlockedDecrement这两个函数说明好了: 
互锁族函数保证增加这个变量值的时候不会被别的线程修改寄存器,考虑如下伪码: 

mov eax, A 
inc eax 
mov A, eax 

如果这段代码存在于2个任务,task1和task2,然后发生如下调度过程 

;task1执行,设A = 0 
mov eax, A 
inc eax 
;系统切换到task2,保存task1的上下文(包括eax的值,eax = 1) 
mov eax, A 
inc eax 
mov A, eax 
;系…


这位兄弟说的比较明确.但这种情况对于BOOL和bool变量的赋值有什么不同么?

#28


根据操作系统CPU不同,以及存贮变量的结构,决定是否用保护。
32位CPU,分两种情况。
在结构中,边界对齐为1/2,请保护
普通变量,或边界对齐为4/8,不用保护
多线程读取,使用volatile是明智之举

#29


MARK,

#30


引用 7 楼 cnzdgs 的回复:
因为在判断为1之后,设置为0之前

这个很好,这是个需要注意的地方。

引用 24 楼 hxfjb 的回复:
一个汇编指令是一个原子操作,要么成功,要么不成功。
因此在执行一个指令的时候,如果操作数是内存中的4个字节的话,此时别的线程、cpu是无法在此时占用总线的。因此不用加锁。

如果是单核,汇编指令是原子操作。如果是多核,就并行执行了。
4个字节必须是按4字节边界对齐的,而且是基本数据类型,在32位机上才可以一个cycle内完成操作。

#31


变量只和作用域有关系  在相同的作用域里,如果引用类型就肯定要。 

如果值类型就不需要。

#1


谁说的

#2


这不是从汇编角度能解释的问题。
是否需要同步要视具体应用情况而定。

#3


谁说不需要

#4


要的呢,需要使用volatile作为修饰符。假如你定义一个全局变量bool isExist控制线程是否退出,那么这个变量这么声明:
static volatile bool isExist;
线程函数类似于:
DWORD ThreadProc(LPVOID)
{
   isExist=true;
   while(isExist)
   {
      //do something
   }
}
如果不使用volatile,只要while循环不去修改isExist的值,那么编译器会优化全局变量的处理,对于那个线程而言,isExist恒为true;这个代码等价于:
DWORD ThreadProc(LPVOID)
{
   isExist=true;
   while(true)
   {
      //do something
   }
}
这样一来,别的线程无法控制当前线程的退出。
使用volatile表示变量会不经意地被修改,它告诉编译器不要对此变量作优化。因此isExist将以真实的值呈现出来。

#5


楼上的几位兄弟,你们能否先问一下身边的牛人们,再跟我解释?

一位IT业16年的老前辈跟我说过,bool变量不需要同步保护,BOOL变量需要同步保护,是与操作系统的操作粒度有关.windows的操作粒度为16个字节(再往后,就不记得他说的其它的话了).

2楼和4楼的回答明显不负责任.而5楼的兄弟的回答好像是从另外一个角度给了解释.....

至于3楼的回答,前提就是需要使用同步保护的时候,来讨论这两种变量的不同.不明白?

还是想寻求高手的解答,如何从汇编的角度来解释为什么bool不需要同步保护???

盼高手!!!!

#6


“楼上的几位兄弟,你们能否先问一下身边的牛人们,再跟我解释? ”
“2楼和4楼的回答明显不负责任”

既然身边有牛人,怎么不直接请教,何必来这里问。

#7


16年可以称为前辈,但前辈说的话不一定就是正确的,而且即使是正确的说法,也是有其适用范围的,不要断章取义。
如果按编程的时间来说,我是从1985年开始学习编程的,可能比那位前辈还前辈,我身边很难找到时间更长的“牛人”来问。不过我说的话也不见得是对的,需要你自己来思考和验证。

前面已经说了,是否需要同步要视具体应用情况而定。
单任务时代没有同步的概念;单处理器环境如果是原子指令访问变量,可以确保在修改过程中不会运行其它线程,这个可以从汇编角度来解释;汇编指令是在一个处理器中执行的,多处理器环境下已经不能再用汇编来考虑同步问题了。
bool变量只有一位有效,只有0和1两种状态,如果只是要给它赋值而不考虑其值对其它线程的影响,那么可以不用同步。但很多时候程序并非这么简单。
例如:假设你想要用一个bool变量作为线程互斥的开关,同一时间只允许一个线程执行某段代码。通常会想到的做法是:在开始时判断变量的值,当为0时等待重试;当为1时,先把变量设置为0,在执行完这段代码后再把变量设置为1。这种做法就隐藏着问题,因为在判断为1之后,设置为0之前,可能其它线程也会执行到判断变量的地方,由此导致多个线程同时执行了这段代码。
再举个复杂点的例子:当同时执行的多个任务之间存在复杂的逻辑关系时,可能多个线程需要使用同一个bool变量进行逻辑运算(与、或、非、异或等)时,处理器需要先从内存中读出数据,经过运算后再把结果保存回内存中,在运算过程中,其它处理器中运行的线程也可能要对此变量进行类型的运算,如果没有考虑同步,就会产生错误的结果。

#8


呵呵,可能我上面说的话偏颇.

但我只想知道如何从汇编的角度考虑为什么需要对变量进行同步保护.

为什么这么多高手看不懂我题目里的意思呢?呵呵.....

如果cnzdgs前辈有时间,再麻烦您从汇编的角度来给我解释一下为什么变量需要进行同步保护.多谢了!!!

#9


TO rageliu:

难道你认为我是*么?即使我能再次去请教他,知识不嫌多,我也可以到CSDN上发帖子来请教其它高手吧?
而你回的"既然身边有牛人,怎么不直接请教,何必来这里问。", 是在用讨论问题的态度来跟我交流么?我说那些兄弟(可能也包括你)回答帖子不负责任,我说错了么?有用"谁说的"这三个字就算是回答问题的么?

如果有兴趣讨论,请摆正你的态度,好么?

感激不尽!!

#10


偶是新手    关注ing

#11


偶不是高手,偶也想问:谁说的?为什么?

#12


你的言下之意,是已经认定你的说法是对的,只是要解释而已。

我说“谁说的”,并没有其他意思,只是表示你的说法我不同意,看看你说这话是否有你的运用环境而已

楼主用不着激动,如果你认为我语气不对,当我没说,不用“难道你认为我是*么?”这样吧

#13


哈,看来,星星多不多和水平高不高,是没有必然关系的.

还是盼高人指点.

#14


该回复于2008-08-19 17:13:21被版主删除

#15


g_bValue = false;
0041151E  mov         byte ptr [g_bValue (41905Ch)],0 
g_bValue ++;
00411525  mov         al,byte ptr [g_bValue (41905Ch)] 
0041152A  mov         byte ptr [ebp-0C5h],al 
00411530  mov         byte ptr [g_bValue (41905Ch)],1 

g_nValue = FALSE;
0041157E  mov         dword ptr [g_nValue (419060h)],0 
g_nValue ++;
00411588  mov         eax,dword ptr [g_nValue (419060h)] 
0041158D  add         eax,1 
00411590  mov         dword ptr [g_nValue (419060h)],eax 

实际上不bool在这里就是BYTE,BOOL是DWORD,仅仅这个差别而已,如果你一定要对 bool 做加法那么在多线程中还是小心为妙
对于加减法可以使用InterlockedIncrement和InterlockedDecrement来在多线程中保证原子操作

#16


当然如果你要做的只是赋值而已,大可不必折腾

#17


当然,如果你要做的仅仅是一个指令就可以搞定的赋值,大可不必折腾

#18


..........

#19


多谢qrlvls的回答,请问,从汇编的角度,如何看一个变量需要被同步保护.

#20


引用 19 楼 yysbest 的回复:
多谢qrlvls的回答,请问,从汇编的角度,如何看一个变量需要被同步保护.


高级语言的一句话可能对应汇编的好几句,假设我们对一个变量等的操作,对应汇编有1 2 3这三个步骤。可能我们在线程1里面执行了1 2这两步。此时发生了线程切换,去到了线程2,刚好线程2里面修改了变量。然后切换回线程1,线程1接着3开始执行,这时候变量其实已经被线程2修改了,可是线程1并不知道,这就出现了问题。

上面我们说“对应汇编有1 2 3这三个步骤”,而如果对变量的操作就一步,直接完成,很多问题就可以避免。

说的比较乱,只是举个例好说明,好多地方是否需要同步,还是看具体环境

#21


就拿InterlockedIncrement和InterlockedDecrement这两个函数说明好了:
互锁族函数保证增加这个变量值的时候不会被别的线程修改寄存器,考虑如下伪码: 

mov eax, A 
inc eax 
mov A, eax 

如果这段代码存在于2个任务,task1和task2,然后发生如下调度过程 

;task1执行,设A = 0 
mov eax, A 
inc eax 
;系统切换到task2,保存task1的上下文(包括eax的值,eax = 1) 
mov eax, A 
inc eax 
mov A, eax 
;系统切换到task1,保存task2的上下文,恢复的task1的上下文(注意:eax = 1) 
mov A,eax 

结果A = 1 

然而,如果task1和task2顺序执行: 
;task1执行,设A = 0 
mov eax, A 
inc eax 
mov A, eax 
;系统切换到task2 
mov eax, A 
inc eax 
mov A, eax 

结果A = 2 


#22


引用 7 楼 cnzdgs 的回复:
16年可以称为前辈,但前辈说的话不一定就是正确的,而且即使是正确的说法,也是有其适用范围的,不要断章取义。 
如果按编程的时间来说,我是从1985年开始学习编程的,可能比那位前辈还前辈,我身边很难找到时间更长的“牛人”来问。不过我说的话也不见得是对的,需要你自己来思考和验证。 

前面已经说了,是否需要同步要视具体应用情况而定。 
单任务时代没有同步的概念;单处理器环境如果是原子指令访问变量,可以确保在修…

呵呵,那位大侠开始搞编程的时候,我才三岁,还在托儿所呢!

#23


cnzdgs编程之后的第2年我才出生.

对于这个问题来说,如果只是一个原子操作,不用保护,和操作粒度有啥绝对关系?

#24


尝试解释一下:
cpu有的是32位,有的是64位。
而BOOL其实是一个DWORD,在32位就是4个字节。
一个汇编指令是一个原子操作,要么成功,要么不成功。
因此在执行一个指令的时候,如果操作数是内存中的4个字节的话,此时别的线程、cpu是无法在此时占用总线的。因此不用加锁。
因此反汇编32位系统上的Interlocked***函数的时候,很多时候你不会看到汇编指令的lock前缀。

但增加该前缀总是安全的。

#25


引用 20 楼 rageliu 的回复:
引用 19 楼 yysbest 的回复:
多谢qrlvls的回答,请问,从汇编的角度,如何看一个变量需要被同步保护. 
 

高级语言的一句话可能对应汇编的好几句,假设我们对一个变量等的操作,对应汇编有1 2 3这三个步骤。可能我们在线程1里面执行了1 2这两步。此时发生了线程切换,去到了线程2,刚好线程2里面修改了变量。然后切换回线程1,线程1接着3开始执行,这时候变量其实已经被线程2修改了,可是线程1并不知道,这就出现了问题。 

上…


呵呵,多谢兄弟的解释,有点明确了..你说的情况是存在的,所以在这个时候,必须使用同步保护.

那么,对于BOOL和bool,在这种情形下,有什么不同么?是不是对BOOL的赋值需要3步,而对bool变量的赋值只需要1步,所以就不需要同步保护了,是不是这个意思?

#26


引用 24 楼 hxfjb 的回复:
尝试解释一下: 
cpu有的是32位,有的是64位。 
而BOOL其实是一个DWORD,在32位就是4个字节。 
一个汇编指令是一个原子操作,要么成功,要么不成功。 
因此在执行一个指令的时候,如果操作数是内存中的4个字节的话,此时别的线程、cpu是无法在此时占用总线的。因此不用加锁。 
因此反汇编32位系统上的Interlocked***函数的时候,很多时候你不会看到汇编指令的lock前缀。 

但增加该前缀总是安全的。


呵呵,这个应该有些道理的

我一直在找官方的正归解释,但现在还没找到...

#27


引用 21 楼 paerxiushi 的回复:
就拿InterlockedIncrement和InterlockedDecrement这两个函数说明好了: 
互锁族函数保证增加这个变量值的时候不会被别的线程修改寄存器,考虑如下伪码: 

mov eax, A 
inc eax 
mov A, eax 

如果这段代码存在于2个任务,task1和task2,然后发生如下调度过程 

;task1执行,设A = 0 
mov eax, A 
inc eax 
;系统切换到task2,保存task1的上下文(包括eax的值,eax = 1) 
mov eax, A 
inc eax 
mov A, eax 
;系…


这位兄弟说的比较明确.但这种情况对于BOOL和bool变量的赋值有什么不同么?

#28


根据操作系统CPU不同,以及存贮变量的结构,决定是否用保护。
32位CPU,分两种情况。
在结构中,边界对齐为1/2,请保护
普通变量,或边界对齐为4/8,不用保护
多线程读取,使用volatile是明智之举

#29


MARK,

#30


引用 7 楼 cnzdgs 的回复:
因为在判断为1之后,设置为0之前

这个很好,这是个需要注意的地方。

引用 24 楼 hxfjb 的回复:
一个汇编指令是一个原子操作,要么成功,要么不成功。
因此在执行一个指令的时候,如果操作数是内存中的4个字节的话,此时别的线程、cpu是无法在此时占用总线的。因此不用加锁。

如果是单核,汇编指令是原子操作。如果是多核,就并行执行了。
4个字节必须是按4字节边界对齐的,而且是基本数据类型,在32位机上才可以一个cycle内完成操作。

#31


变量只和作用域有关系  在相同的作用域里,如果引用类型就肯定要。 

如果值类型就不需要。