首页 > 试题广场 >

两个线程并发执行以下代码,假设a是全局变量,那么以下输出__

[不定项选择题]
两个线程并发执行以下代码,假设a是全局变量,初始为1,那么以下输出______是可能的?
void foo(){
    a=a+1;
    printf("%d ",a);
}
  • 3 2
  • 2 3
  • 3 3
  • 2 2

涉及到2次取a的值,都可以覆盖。每次有两种取值,排列组合,4种情况。

发表于 2019-09-23 15:36:39 回复(0)
注意printf是函数,里面参数是值传递,所以会出现3,2的情况,线程2把数据改成3了,但是线程1已经到printf函数里面了,所以还是2
发表于 2018-09-18 11:19:46 回复(1)
这题答案怎么又变成ABC了。。。
发表于 2016-03-29 21:25:01 回复(0)
3,2   2,3   和   3,3这3种情况都很好理解,我就不赘述了。
我来讲解一下为什么会输出2,2吧。

不知道朋友们有没有学过X86汇编,是这样的,你看a=a+1这一条语句,看起来是一条语句,实际上这条语句会被编译成大概3条指令的。
首先是先计算出a+1的值,那么就是:
mov  al,a   (把a的值赋给寄存器al)
add   al,1  (寄存器al的值加1)

计算完a+1的值后,会再进行赋值,对应的指令是 :
mov  a,al(把寄存器al里的值赋给变量a)
大概就是这样的一个过程,3条指令。

你想既然这样,第1个进程可能是执行到   刚好计算完a+1的值,还没给变量a赋值(这时al里保存的是2,a依然是1),CPU就转换到执行第2个进程了,第2个进程完成这3条指令后,a的值为2,输出2,然后又回到第1个进程,这时开始执行mov  a,al(把寄存器al里的值2赋给2),所以a的值为2,也输出2

希望你们能看到我的观点,如果看不懂的话也没关系,简单地说就是:a=a+1这一句实际上执行是由很多条指令组成的
发表于 2018-03-22 21:03:48 回复(11)
编译出来(一般来讲)大概会是这么几步:
  • 读a
  • +1
  • 写a
  • 再读a (这个可能被编译器优化掉)
  • 写到屏幕上

两个线程,[1]和[2]:

A
  • [1]读a
  • [1]+1
  • [1]写a // a =2
  • [1]再读a
  • [2]读a
  • [2]+1
  • [2]写a // a = 3
  • [2]再读a
  • [2]写到屏幕上 // 3_
  • [1]写到屏幕上 // 2_
B

先执行所有[1]再执行所有[2]就得到B了,不解释了。

C
  • [1]读a
  • [1]+1
  • [1]写a // a = 2
  • [2]读a
  • [2]+1
  • [2]写a // a = 3
  • [1]再读a
  • [2]再读a
  • ...
就得到3_3_了,但如果没有再读a的步骤,就是_2_3或者_3_2了。

D
  • [1]读a
  • [2]读a
  • [1]+1
  • [2]+1
  • [1]写a // a = 2
  • [2]写a // a = 2
  • ....
就得到2_2_了
发表于 2015-09-04 18:02:24 回复(13)
我怎么觉得都可能呢?

假设线程x和y同时执行,x和y可随时被抢占,a的初始值为1

A:3, 2
y先执行++a,a为2;
y再执行printf,a入栈,在打印到终端之前切换到x
x执行++a,a为3;
x执行printf,输出3;再切换到y
y执行打印,输出2

B:2 3
x先执行++a,a为2;
x再执行printf,输出2;切换到y
y执行++a,a为3;
y执行printf,输出3;

C:3 3
x先执行++a,a为2;切换到y
y执行++a,a为3;
y执行printf,输出3;切换到x
x执行printf,输出3

D:2 2
类似C,a的初始值为0即可

这里关键有两点:
(1)两个线程可随时被抢占
(2)++a和printf不是原子指令,可随时被打断;特别注意函数printf,a作为参数压栈后,a再变化则不会影响输出(printf实际打印的是压栈的参数,是值拷贝的栈变量)
发表于 2015-04-13 17:52:39 回复(16)
++i不是原子操作
发表于 2015-07-30 23:33:44 回复(8)
2、3情况:线程1读入值1,增加1,值2返回内存,显示2;然后,线程2读入值2,增加1,值3返回内存,显示值3。
3、2情况,根据对称性,与2、3情况相同。
2、2情况,线程1读入1后,线程2运行,读入1,然后线程1运行,增加1,值2返回内存,显示2,然后线程2运行,增加1,值2返回内存,显示2。(线程1、2可互换)
3、3情况:线程1读入1,增加1,值2返回内存,结果2显示前线程2运行,增加1,值2返回内存,显示3,然后线程1运行,从内存读入3,显示3。
编辑于 2015-08-24 11:47:39 回复(2)
多线程在操作一个全局变量时,
①首先每个线程都会将全局变量copy一份到线程自己的内存,内部操作副本;(此时全局变量值不变)
②再更新全局变量(全局变量值改变)。
③读取全局变量(改变后的),输出。
那么产生不同结果的原因就在于多个线程在①,②,③步的执行顺序;

如题:线程用A,B命名;
1.
A复制a=1到线程内存中a1= 1,a1为a的副本;
a1= ++a; a1 = 2;
A更新a的值,a=2;
A读取a,并输出  a = 2; Point1;
B读取a的副本,a2位a的副本,a2=2;
a2 = ++a;a2 = 3;
B更新a的值,a=3;
Point2;
B读取a,并输出  a = 3;
Point2;
>>>>>>>因此  可能的输出结果为:2,3
2.
A,B线程执行顺序相反,B先执行,输出2,A后执行输出3;
>>>>>>>因此  可能的输出结果为:3,2
3.
在上述1. Point1 处A输出a值的操作 , 改到 Ponit2 处,则A,B均输出3;
>>>>>>>因此  可能的输出结果为:3,3
4.
A,B存在读取a(a=1时)的副本,此时,a1 = 1,a2 =1;
A,B操作副本之后,a1=2,a2=2;
A,B更新a的值
此时a=2;
A,B读取a值,并输出,a=2;
此时,A,B的执行顺序不影响结果;
>>>>>>>因此  可能的输出结果为:2,2
个人理解,若有不对的地方, 请见谅 ,欢迎指正。

编辑于 2016-10-24 12:04:18 回复(0)
考点有++a是原子操作 不可被中断 printf为非原子操作 可被中断 还要考虑缓冲区的问题 还要考虑参数进栈顺序 
对于A
第一个线程执行
++a;
a的进栈操作 但并为输出 此时a的值2保留在缓冲区中 发生中断 
第二个线程执行 
++a;
完整的printf操作 输出3 然后让出cpu 线程1继续 输出2

对于B
第一个线程执行至函数结束 输出2
然后线程二执行至函数结束  输出3

对于C
线程一执行 
++a;
然后发生中断 
线程二执行直至函数结束 此时输出3 让出cpu 
与A的区别是函数的进栈顺序 此时为3

D就不理解了 已经初始化为1 哪里来的0



发表于 2015-07-23 07:42:22 回复(2)
注意a=a+1, ,printf 都不是原子操作
可以理解为:
1. 从内存读取a
2. a值+1
3. a值写入内存
4. 从内存读出a
5. a值压入printf栈
6. printf显示到屏幕
答案ABCD
(A)
1.  P1从内存读出a(a=1)
2.  P1使a值+1,然后写入内存
3.  P2从内存读出a(a=2)
4.  P1从内存读出a,然后压入printf栈(预备打印,此后a的值改变不影响printf结果)(a=2)(3.4顺序可换)
5.  P2使a值+1,然后写入内存(a=3)
6.  P2从内存读出a,然后压入printf栈(预备打印,此后a的值改变不影响printf结果)(a=3)
7.  P2 printf显示到屏幕(a=3)
8.  P1 printf显示到屏幕(a=2)
9.  结果 3 2

(B)
1.  P1从内存读出a(a=1),然后使a值+1,然后写入内存,然后读出并压入printf栈,显示到屏幕(a=2)
2.  P2从内存读出a(a=2),然后使a值+1,然后写入内存,然后读出并压入printf栈,显示到屏幕(a=3)
3.  结果 2 3

(C)
1.  P1从内存读出a(a=1),然后使a值+1,然后写入内存(a=2)
2.  P2从内存读出a(a=2),然后使a值+1,然后写入内存(a=3)
3.  P1从内存读出a,然后压入printf栈,显示到屏幕(a=3)
4.  P2从内存读出a,然后压入printf栈,显示到屏幕(a=3)
5.  结果 3 3

(D)
1.  P1从内存读出a(a=1)
2.  P2从内存读出a(a=1)
3.  P1使a值+1,然后写入内存,然后读出并压入printf栈(a=2)
4.  P2使a值+1,然后写入内存,然后读出并压入printf栈(a=2)
5.  printf显示
6.  结果 2 2
发表于 2018-01-04 16:49:18 回复(1)
发现我错了,这是C语言代码,不是java代码,而我还用java的思路去解决。
对于C语言来说,++i是原子操作。
发表于 2015-09-01 21:12:27 回复(0)
假设两个线程分别是A和B

首先2 3是属于正常现象的,执行顺序为

A线程   a = a+1;     //a=2
A线程   printf("%d ",a);    //输出2
B线程   a = a+1;     //a=3
B线程   printf("%d ",a);    //输出3

3 3 的执行顺序为:
A线程   a = a+1;     //a=2
B线程   a = a+1;     //a=3
A线程   printf("%d ",a);    //将a传入printf()函数前a已经是3了,输出3
B线程   printf("%d ",a);    //输出3

3 2 的原因主要是因为printf()函数,可以将调用函数和输出结果看成是两步
执行顺序为:
A线程   a = a+1;     //a=2
A线程   printf("%d ",a);    //调用printf()函数,传入a的值是2,因为是值传递,相当于copy了一份,所以在printf函数内部永远是2
B线程   a = a+1;     //a=3
B线程   printf("%d ",a);    //调用printf()函数,传入a的值是3,因为是值传递,相当于copy了一份,所以在printf函数内部永远是3
B线程  pirntf()函数执行完毕输出3
A线程  pirntf()函数执行完毕输出2

2 2 的原因主要是因为a = a+1  如果细分是可以分为两步的,第一步计算,第二部赋值
可以看成  temp = a+1;
a = temp;

则2 2 的执行顺序为:

A线程 temp1 = a+1; //temp1=2
B线程 temp2= a+1;     //temp2=2,  这里计算出temp1就直接计算temp2了,导致temp1的结果没有用到
A线程 a = temp1; //a=2
B线程   a = temp2;     //a=2
A线程   printf("%d ",a);    //输出2
B线程   printf("%d ",a);    //输出2



编辑于 2019-05-17 15:42:46 回复(0)
这题的关键在于C语言中 ++a 算不算原子操作吧。
我在网上看到有个博主编译出来不是原子的,但我用GCC 4.8.1编译出来的是原子操作。
所以这题是不是有点问题。
#include <stdio.h>
int main() {
    int a = 0;
    ++a;
    return 0;
}
它的汇编
_main:
LFB8:
	/*省略*/
	movl	$0, 12(%esp)
	addl	$1, 12(%esp)
	movl	$0, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
直接用addl+1了。
发表于 2016-03-06 16:15:03 回复(4)
为嘛觉得A不可能啊。
++a; (1)++a要到下一条语句才自增
printf("%d ",a); (2)
B. T1的(1)(2)执行完,再执行T2的(1)(2);
C. T1执行完(1),但是已经脱离(1),此时a为2,轮T2执行完(1)也脱离(1),此时a为3, 再轮T1执行(2),a输出3,再轮T2执行(2),输出3;
D. T1执行完(1),++a此时a还是1,暂时未变,轮T2执行(1)仍是1,暂时未变, 再轮T1执行(2),a变成2,再轮T2执行(2),仍为2。
发表于 2015-06-27 21:18:37 回复(0)
个人愚见D选项,第一个线程先读入a放到寄存器里,此时线程1的a是1 然后切换需要到线程2,此时线程1的寄存器被保存现场,线程2也读入a,此时线程2的a也是1 然后再切换回线程1,线程2的寄存器保存现场 ,线程1的a加1变成2,压入栈里,输出 然后再切换回线程2,线程2的a加1变成2,也压入栈里,输出 个人觉得要理解的就是➕1的操作需要用到ALU,所以读入CPU的寄存器里,然后printf输出需要压栈写回内存 如有错误欢迎指正
发表于 2022-08-03 10:18:04 回复(0)
两个读取指令都可以放在最后,都读取到3
发表于 2022-01-26 10:46:27 回复(0)
3个过程, mov al a add al mov a al 如果都是2,则中间可能不执行,直接被切换走了
发表于 2020-04-25 18:08:16 回复(0)
两句都不是原子操作,还可以拆分:
  • a=a+1:  读a ->加1 ->写a
  • print a:  读a ->屏幕a
发表于 2020-03-23 15:53:24 回复(0)
printf分成两步:取值,输出。
发表于 2019-07-30 20:36:41 回复(0)