免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123下一页
最近访问板块 发新帖
查看: 12587 | 回复: 23
打印 上一主题 下一主题

再论int变量赋值的原子性 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2010-10-26 11:22 |只看该作者 |倒序浏览
前几天做项目,遇到了多线程读写全局int变量是否需要进行保护的问题,于是搜索到了四年前几位前辈对这个问题的精彩的讨论:
http://www.chinaunix.net/jh/23/804826.html

整篇帖子看下来,给我醍醐灌顶的感觉,实在是受益非浅;而这种做技术来不得一丝马虎的执着才是一个程序员所应该具备的执着。

唯一美中不足的,是整个帖了结束的时候,没有一个十分明确的结论到底int变量的多线程操作不加保护有没有危险,如果有危险,是在什么样的条件?而我遇到的问题很单一:CPU为ARM9,在内存没有对齐的情况下,会不会存在一个线程给一个int变量赋值没有结束而被另一个线程读走的危险?

以下是我的实验过程

实验代码:

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <pthread.h>
  4. struct STRU
  5. {
  6.     char a;
  7.     int  b;
  8. }__attribute__((packed));
  9. struct STRU v;

  10. void threadfun1(void)
  11. {
  12.     int i = 0;
  13.     while(1)
  14.     {
  15.         if(i%2 == 0)
  16.         {
  17.             v.b = 0x55555555;
  18.         }
  19.         else
  20.         {
  21.             v.b = 0xaaaaaaaa;
  22.         }
  23.         i++;
  24.     }      
  25. }

  26. void threadfun2(void)
  27. {   int i;
  28.    
  29.     while(1)
  30.     {   
  31.         i = v.b;
  32.         if((i != 0x55555555) && (i != 0xaaaaaaaa))
  33.         {   
  34.             printf("v.b = %x\n", i);
  35.         }
  36.     }
  37. }

  38. void main(void)
  39. {
  40.     pthread_t tidFun1;
  41.     pthread_t tidFun2;
  42.     pthread_create(&tidFun1, NULL, threadfun1, NULL);
  43.     pthread_create(&tidFun2, NULL, threadfun2, NULL);
  44.     while(1)
  45.     {
  46.         sleep(5);
  47.     }
  48. }
复制代码


如上,结构体struct STRU中的成员变量b,因为使用了宏__attribute__((packed)),所以采取的是字节对齐的方式而不是内存对齐的方式,用arm-linux-gcc来编译并反汇编,线程1的函数代码如下:

  1. 00008520 <threadfun1>:
  2.     8520:       e1a0c00d        mov     ip, sp
  3.     8524:       e92dd800        stmdb   sp!, {fp, ip, lr, pc}
  4.     8528:       e24cb004        sub     fp, ip, #4      ; 0x4
  5.     852c:       e24dd004        sub     sp, sp, #4      ; 0x4
  6.     8530:       e3a03000        mov     r3, #0  ; 0x0
  7.     8534:       e50b3010        str     r3, [fp, #-16]
  8.     8538:       e51b3010        ldr     r3, [fp, #-16]
  9.     853c:       e2233001        eor     r3, r3, #1      ; 0x1
  10.     8540:       e2033001        and     r3, r3, #1      ; 0x1
  11.     8544:       e20330ff        and     r3, r3, #255    ; 0xff
  12.     8548:       e3530000        cmp     r3, #0  ; 0x0
  13.     854c:       0a00000d        beq     8588 <threadfun1+0x68>
  14.     8550:       e59f2094        ldr     r2, [pc, #148]  ; 85ec <.text+0x1a8>
  15.     8554:       e3a03000        mov     r3, #0  ; 0x0
  16.     8558:       e3833055        orr     r3, r3, #85     ; 0x55
  17.     855c:       e5c23001        strb    r3, [r2, #1]
  18.     8560:       e3a03000        mov     r3, #0  ; 0x0
  19.     8564:       e3833055        orr     r3, r3, #85     ; 0x55
  20.     8568:       e5c23002        strb    r3, [r2, #2]
  21.     856c:       e3a03000        mov     r3, #0  ; 0x0
  22.     8570:       e3833055        orr     r3, r3, #85     ; 0x55
  23.     8574:       e5c23003        strb    r3, [r2, #3]
  24.     8578:       e3a03000        mov     r3, #0  ; 0x0
  25.     857c:       e3833055        orr     r3, r3, #85     ; 0x55
  26.     8580:       e5c23004        strb    r3, [r2, #4]
  27.     8584:       ea000014        b       85dc <threadfun1+0xbc>
  28.     8588:       e59f205c        ldr     r2, [pc, #92]   ; 85ec <.text+0x1a8>
  29.     858c:       e3a03000        mov     r3, #0  ; 0x0
  30.     8590:       e1e03003        mvn     r3, r3
  31.     8594:       e2033055        and     r3, r3, #85     ; 0x55
  32.     8598:       e1e03003        mvn     r3, r3
  33.     859c:       e5c23001        strb    r3, [r2, #1]
  34.     85a0:       e3a03000        mov     r3, #0  ; 0x0
  35.     85a4:       e1e03003        mvn     r3, r3
  36.     85a8:       e2033055        and     r3, r3, #85     ; 0x55
  37.     85ac:       e1e03003        mvn     r3, r3
  38.     85b0:       e5c23002        strb    r3, [r2, #2]
  39.     85b4:       e3a03000        mov     r3, #0  ; 0x0
  40.     85b8:       e1e03003        mvn     r3, r3
  41.     85bc:       e2033055        and     r3, r3, #85     ; 0x55
  42.     85c0:       e1e03003        mvn     r3, r3
  43.     85c4:       e5c23003        strb    r3, [r2, #3]
  44.     85c8:       e3a03000        mov     r3, #0  ; 0x0
  45.     85cc:       e1e03003        mvn     r3, r3
  46.     85d0:       e2033055        and     r3, r3, #85     ; 0x55
  47.     85d4:       e1e03003        mvn     r3, r3
  48.     85d8:       e5c23004        strb    r3, [r2, #4]
  49.     85dc:       e51b3010        ldr     r3, [fp, #-16]
  50.     85e0:       e2833001        add     r3, r3, #1      ; 0x1
  51.     85e4:       e50b3010        str     r3, [fp, #-16]
  52.     85e8:       eaffffd2        b       8538 <threadfun1+0x18>
  53.     85ec:       000109c5        andeq   r0, r1, r5, asr #19
复制代码


可以看到,对变量v.b的一个int赋值被分成了多条汇编语句,在目标机上运行,打印结果为以下多种结果
v.b = 55aaaaaa
v.b = 555555aa
v.b = aa555555
v.b = aaaa5555
......

由此可见,至少在arm9的CPU上,在非内存对齐的情况下,存在一个线程对全局的int变量赋值没有完成而被另一个线程读走,从而导致未知后果的可能性。

如果是在X86上又会如何?

以下是在X86上的汇编代码:
  1. 08048444 <threadfun1>:
  2. 8048444: 55 push %ebp
  3. 8048445: 89 e5 mov %esp,%ebp
  4. 8048447: 83 ec 10 sub $0x10,%esp
  5. 804844a: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffc(%ebp)
  6. 8048451: 8b 45 fc mov 0xfffffffc(%ebp),%eax
  7. 8048454: 83 e0 01 and $0x1,%eax
  8. 8048457: 85 c0 test %eax,%eax
  9. 8048459: 75 0c jne 8048467 <threadfun1+0x23>
  10. 804845b: c7 05 15 97 04 08 55 movl $0x55555555,0x8049715
  11. 8048462: 55 55 55
  12. 8048465: eb 0a jmp 8048471 <threadfun1+0x2d>
  13. 8048467: c7 05 15 97 04 08 aa movl $0xaaaaaaaa,0x8049715
  14. 804846e: aa aa aa
  15. 8048471: 83 45 fc 01 addl $0x1,0xfffffffc(%ebp)
  16. 8048475: eb da jmp 8048451 <threadfun1+0xd>
复制代码


由上看到,X86对这种字节对齐的int变量也是只用了一条movl语句进行赋值,所以不会存在赋值到一半被另一个线程读走的情况,通过运行实验程序也证明了这个问题。

最后,虽然题目是“再论int变量赋值的原子性”,但如果其内容能达到前一帖子的百分之一二有用,我就心满意足了

论坛徽章:
0
2 [报告]
发表于 2010-10-26 11:44 |只看该作者
好贴! 试试代码去。。

论坛徽章:
0
3 [报告]
发表于 2010-10-26 12:52 |只看该作者
不错. 话说当初就是那个帖子把我吸引到了CU.
  我的工作和UNIX毫无关系.但编程的东西大家是相通的.

论坛徽章:
0
4 [报告]
发表于 2010-10-26 16:39 |只看该作者
比较完美的总结 学习

论坛徽章:
0
5 [报告]
发表于 2010-10-26 17:23 |只看该作者
汇编指令是可以被中断的。

论坛徽章:
0
6 [报告]
发表于 2010-10-26 17:28 |只看该作者
弱问,为什么我把程序down下来,运行了,结果没有输出?!。。。

论坛徽章:
0
7 [报告]
发表于 2010-10-27 17:36 |只看该作者
弱问,为什么我把程序down下来,运行了,结果没有输出?!。。。
hdc1112 发表于 2010-10-26 17:28



你是什么CPU?如果是在X86上,是没有输出的,因为是X86上是一条汇编指令就完成赋值了,保证了原子性。

论坛徽章:
0
8 [报告]
发表于 2010-10-27 18:16 |只看该作者
好贴!收藏了

论坛徽章:
0
9 [报告]
发表于 2010-10-27 21:03 |只看该作者
你是什么CPU?如果是在X86上,是没有输出的,因为是X86上是一条汇编指令就完成赋值了,保证了原子性。 ...
freedon 发表于 2010-10-27 17:36

你这是仅仅对于单CPU的情况,如果是多核你这条就不管用了,movl是访问的内存,在这个核心读取的时候那边可能正在修改。。
在我的机器上就输出了v.b=0;

论坛徽章:
0
10 [报告]
发表于 2010-10-28 08:58 |只看该作者
你这是仅仅对于单CPU的情况,如果是多核你这条就不管用了,movl是访问的内存,在这个核心读取的时候那边可 ...
davelv 发表于 2010-10-27 21:03



    多核的没试过,或许会存在你说的那个问题吧。
不过你v.b=0的情况,并不是多核的问题,应该是线程2先于线程1运行所致。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP