全部博文(73)
分类: LINUX
2009-04-23 16:23:25
pthread之gcc优化参数(汇编分析)
在多线程编写的过程中,我们常常需要在多个线程之前共享一个变量,用于传递线程的状态或者步骤,但是这也常常引发
一些另人沮丧的BUG,而这种BUG看上去与gcc的优化参数有关。当不对代码优化时,代码可以很好的执行,但将代码放入
真实环境,重新编译之后,死循环,或者未知错误不断。
下面的测试用例用于测试线程之间共享变量来控制程序时出现的问题,我们将通过对生成的汇编语言的分析来寻找答案。虽然,
我们可以用下面的方法解决问题,但从多线程内存屏障以及内存可视性的规则来看,我们本来就不应该将内存的问题指靠特定
的编译器或者编译选项。
我们的目标是用最简单的例子说明晦涩的内容:)
源代码文件:
main.h 定义共享变量
main.c 主线程代码,读共享变量
thread.c 线程体代码,写共离变量
汇编代码文件:
main0.s 不用优化参数生成的汇编结果,且不加volatile关键字,程序正常运行
main2.s 用优化参数生成的汇编结果,且不加volatile关键字,程序进入死循环
main_v2.s 用优化参数生成的汇编结果,且加volatile关键字,程序正常运行
直接进入代码分析吧,先来看看main.h
#ifndef MAIN_H
#define MAIN_H
#include
#include
#include
#include
#include
#include
#include
typedef struct test
{
volatile int a; //
//int a; //下面的分析有不有加volatile关键字,请参看上面对文件的说明部分
int b;
}Test;
#endif
main.c的源代码:
#include "main.h"
extern void * change( void * arg );
int main()
{
Test x = { 1, 0 };
pthread_t pid;
if ( pthread_create( &pid, 0,
change, &x ) )
{
printf( "Create thread
error\n" );
exit( -1 );
}
//数据测试
while( x.a ) //也许你会觉得这个循环太简单了,所以编译才会得出下面的结果,但是事实上,
//即使这儿就算是一个指针也是同样的结果
{
//printf( "%d\n",
x.b );
}
pthread_join( pid, 0 );
printf( "%d\n", x.b );
return 0;
}
看看thread.c这个文件吧,太简单了吧:)
#include "main.h"
void * change( void * arg )
{
Test * x = ( Test * )arg ;
x->a = 0;
x->b = 11;
printf( "XXXX\n" );
return (void *)0;
}
看上面的代码时,最好与
看看编译用的Makefile:
all:run
run: main.c thread.c
gcc -Wall -O2 -c thread.c -o thread.o
gcc -Wall -O2 -c main.c -o main.o
gcc -Wall -O2 -S main.c -o
main.s //生成优化汇编
gcc -Wall -S main.c -o
main0.s //生成未优化汇编
gcc -Wall main.o thread.o -o run
-lpthread
clean:
rm *.log.* run *.out.* *.o *.s
下面看看生成的汇编代码:
1、不用优化参数生成的汇编结果,且不加volatile关键字,程序正常运行
.file "main.c"
.section .rodata
.LC0:
.string "Create thread error\n"
.LC1:
.string "%d\n"
.text
.globl main
.type main,@function
main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
andl $-16, %esp
movl $0, %eax
subl %eax, %esp
movl $1, -8(%ebp)
movl $0, -4(%ebp)
leal -8(%ebp), %eax
pushl %eax
pushl $change
pushl $0
leal -12(%ebp), %eax
pushl %eax
call pthread_create
addl $16, %esp
testl %eax, %eax //pthread_create
return value
je .L2 //if return value equals to zero
subl $12, %esp
pushl $.LC0
//print error info
call printf
addl $16, %esp
subl $12, %esp
pushl $-1
call exit
.L2:( while )
nop
.L3:
cmpl $0, -8(%ebp)
jne .L3 //注意,虽然这儿是一个空循环体,但是每次循环条件还是与栈中数据有关
subl $8, %esp
pushl $0
pushl -12(%ebp)
call pthread_join
addl $16, %esp
subl $8, %esp
pushl -4(%ebp)
pushl $.LC1
call printf
addl $16, %esp
movl $0, %eax
leave
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux
2、用优化参数生成的汇编结果,且不加volatile关键字,程序进入死循环
.file "main.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC1:
.string "%d\n"
.LC0:
.string "Create thread error"
.text
.p2align 2,,3
.globl main
.type main,@function
main:
pushl %ebp
movl %esp, %ebp
pushl %ebx
subl $20, %esp
andl $-16, %esp //esp
后四位清零
leal -16(%ebp), %ebx //将结构体的地址放入ebx
pushl %ebx //ebx压栈
pushl $change //压栈
pushl $0
leal -20(%ebp), %eax //取pid地址放到eax中
pushl %eax
movl $1, -16(%ebp) //将结构体的内容进行赋值(a)
movl $0, -12(%ebp) //(b)
call pthread_create
addl $16, %esp
testl %eax, %eax //返回值是否为零
jne .L6
movl -16(%ebp), %ecx
testl %ecx, %ecx
.p2align 2,,3
.L3:
jne .L3 //看看这个循环,经过编译之后已经成为一个死循环,每次不进行取数比较,建议看gcc的优化
subl $8, %esp
pushl $0
pushl -20(%ebp)
call pthread_join
popl %eax
popl %edx
pushl 4(%ebx)
pushl $.LC1
call printf
xorl %eax, %eax
movl -4(%ebp), %ebx
leave
ret
.L6:
subl $12, %esp
pushl $.LC0
call puts
movl $-1, (%esp)
call exit
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
3、用优化参数生成的汇编结果,且加volatile关键字,程序正常运行
.file "main.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC1:
.string "%d\n"
.LC0:
.string "Create thread error"
.text
.p2align 2,,3
.globl main
.type main,@function
main:
pushl %ebp
movl %esp, %ebp
pushl %ebx
subl $20, %esp
andl $-16, %esp
leal -16(%ebp), %ebx
pushl %ebx
pushl $change
pushl $0
leal -20(%ebp), %eax
pushl %eax
movl $1, -16(%ebp)
movl $0, -12(%ebp)
call pthread_create
addl $16, %esp
testl %eax, %eax
jne .L6
.p2align 2,,3
.L3:
movl -16(%ebp), %eax #retrive
data from stack
testl %eax, %eax #compare
the value with zero
jne .L3 #while,看看这个循环,每次都取数进行比较
subl $8, %esp
pushl $0
pushl -20(%ebp)
call pthread_join
popl %eax
popl %edx
pushl 4(%ebx)
pushl $.LC1
call printf
xorl %eax, %eax
movl -4(%ebp), %ebx
leave
ret
.L6:
subl $12, %esp
pushl $.LC0
call puts
movl $-1, (%esp)
call exit
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
通过上个三个不同的编译结果能得到什么样的结论?
1、多线程之间数据共享变量引发的问题不是由于pthread引起的,而是由于编译器引起。(当然,本质上是编码的问题造成)
2、volatile关键字(在c++中是mutable)可以阻止gcc对不该优化的地方进行优化。
3、要用内存可视性规则来保障多线程中资源的共享问题,而不应该依赖硬件或者编译器选项