- 论坛徽章:
- 0
|
前几天做项目,遇到了多线程读写全局int变量是否需要进行保护的问题,于是搜索到了四年前几位前辈对这个问题的精彩的讨论:
http://www.chinaunix.net/jh/23/804826.html
整篇帖子看下来,给我醍醐灌顶的感觉,实在是受益非浅;而这种做技术来不得一丝马虎的执着才是一个程序员所应该具备的执着。
唯一美中不足的,是整个帖了结束的时候,没有一个十分明确的结论到底int变量的多线程操作不加保护有没有危险,如果有危险,是在什么样的条件?而我遇到的问题很单一:CPU为ARM9,在内存没有对齐的情况下,会不会存在一个线程给一个int变量赋值没有结束而被另一个线程读走的危险?
以下是我的实验过程
实验代码:
-
- #include <stdio.h>
- #include <unistd.h>
- #include <pthread.h>
- struct STRU
- {
- char a;
- int b;
- }__attribute__((packed));
- struct STRU v;
-
- void threadfun1(void)
- {
- int i = 0;
- while(1)
- {
- if(i%2 == 0)
- {
- v.b = 0x55555555;
- }
- else
- {
- v.b = 0xaaaaaaaa;
- }
- i++;
- }
- }
-
- void threadfun2(void)
- { int i;
-
- while(1)
- {
- i = v.b;
- if((i != 0x55555555) && (i != 0xaaaaaaaa))
- {
- printf("v.b = %x\n", i);
- }
- }
- }
-
- void main(void)
- {
- pthread_t tidFun1;
- pthread_t tidFun2;
- pthread_create(&tidFun1, NULL, threadfun1, NULL);
- pthread_create(&tidFun2, NULL, threadfun2, NULL);
- while(1)
- {
- sleep(5);
- }
- }
复制代码
如上,结构体struct STRU中的成员变量b,因为使用了宏__attribute__((packed)),所以采取的是字节对齐的方式而不是内存对齐的方式,用arm-linux-gcc来编译并反汇编,线程1的函数代码如下:
-
- 00008520 <threadfun1>:
- 8520: e1a0c00d mov ip, sp
- 8524: e92dd800 stmdb sp!, {fp, ip, lr, pc}
- 8528: e24cb004 sub fp, ip, #4 ; 0x4
- 852c: e24dd004 sub sp, sp, #4 ; 0x4
- 8530: e3a03000 mov r3, #0 ; 0x0
- 8534: e50b3010 str r3, [fp, #-16]
- 8538: e51b3010 ldr r3, [fp, #-16]
- 853c: e2233001 eor r3, r3, #1 ; 0x1
- 8540: e2033001 and r3, r3, #1 ; 0x1
- 8544: e20330ff and r3, r3, #255 ; 0xff
- 8548: e3530000 cmp r3, #0 ; 0x0
- 854c: 0a00000d beq 8588 <threadfun1+0x68>
- 8550: e59f2094 ldr r2, [pc, #148] ; 85ec <.text+0x1a8>
- 8554: e3a03000 mov r3, #0 ; 0x0
- 8558: e3833055 orr r3, r3, #85 ; 0x55
- 855c: e5c23001 strb r3, [r2, #1]
- 8560: e3a03000 mov r3, #0 ; 0x0
- 8564: e3833055 orr r3, r3, #85 ; 0x55
- 8568: e5c23002 strb r3, [r2, #2]
- 856c: e3a03000 mov r3, #0 ; 0x0
- 8570: e3833055 orr r3, r3, #85 ; 0x55
- 8574: e5c23003 strb r3, [r2, #3]
- 8578: e3a03000 mov r3, #0 ; 0x0
- 857c: e3833055 orr r3, r3, #85 ; 0x55
- 8580: e5c23004 strb r3, [r2, #4]
- 8584: ea000014 b 85dc <threadfun1+0xbc>
- 8588: e59f205c ldr r2, [pc, #92] ; 85ec <.text+0x1a8>
- 858c: e3a03000 mov r3, #0 ; 0x0
- 8590: e1e03003 mvn r3, r3
- 8594: e2033055 and r3, r3, #85 ; 0x55
- 8598: e1e03003 mvn r3, r3
- 859c: e5c23001 strb r3, [r2, #1]
- 85a0: e3a03000 mov r3, #0 ; 0x0
- 85a4: e1e03003 mvn r3, r3
- 85a8: e2033055 and r3, r3, #85 ; 0x55
- 85ac: e1e03003 mvn r3, r3
- 85b0: e5c23002 strb r3, [r2, #2]
- 85b4: e3a03000 mov r3, #0 ; 0x0
- 85b8: e1e03003 mvn r3, r3
- 85bc: e2033055 and r3, r3, #85 ; 0x55
- 85c0: e1e03003 mvn r3, r3
- 85c4: e5c23003 strb r3, [r2, #3]
- 85c8: e3a03000 mov r3, #0 ; 0x0
- 85cc: e1e03003 mvn r3, r3
- 85d0: e2033055 and r3, r3, #85 ; 0x55
- 85d4: e1e03003 mvn r3, r3
- 85d8: e5c23004 strb r3, [r2, #4]
- 85dc: e51b3010 ldr r3, [fp, #-16]
- 85e0: e2833001 add r3, r3, #1 ; 0x1
- 85e4: e50b3010 str r3, [fp, #-16]
- 85e8: eaffffd2 b 8538 <threadfun1+0x18>
- 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上的汇编代码:
- 08048444 <threadfun1>:
- 8048444: 55 push %ebp
- 8048445: 89 e5 mov %esp,%ebp
- 8048447: 83 ec 10 sub $0x10,%esp
- 804844a: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffc(%ebp)
- 8048451: 8b 45 fc mov 0xfffffffc(%ebp),%eax
- 8048454: 83 e0 01 and $0x1,%eax
- 8048457: 85 c0 test %eax,%eax
- 8048459: 75 0c jne 8048467 <threadfun1+0x23>
- 804845b: c7 05 15 97 04 08 55 movl $0x55555555,0x8049715
- 8048462: 55 55 55
- 8048465: eb 0a jmp 8048471 <threadfun1+0x2d>
- 8048467: c7 05 15 97 04 08 aa movl $0xaaaaaaaa,0x8049715
- 804846e: aa aa aa
- 8048471: 83 45 fc 01 addl $0x1,0xfffffffc(%ebp)
- 8048475: eb da jmp 8048451 <threadfun1+0xd>
复制代码
由上看到,X86对这种字节对齐的int变量也是只用了一条movl语句进行赋值,所以不会存在赋值到一半被另一个线程读走的情况,通过运行实验程序也证明了这个问题。
最后,虽然题目是“再论int变量赋值的原子性”,但如果其内容能达到前一帖子的百分之一二有用,我就心满意足了 |
|