Chinaunix首页 | 论坛 | 博客
  • 博客访问: 192602
  • 博文数量: 73
  • 博客积分: 5000
  • 博客等级: 大校
  • 技术积分: 1160
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-23 15:53
文章分类

全部博文(73)

文章存档

2011年(1)

2009年(72)

我的朋友

分类: LINUX

2009-04-23 16:23:25

pthreadgcc优化参数(汇编分析)

在多线程编写的过程中,我们常常需要在多个线程之前共享一个变量,用于传递线程的状态或者步骤,但是这也常常引发
一些另人沮丧的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 3.2.2-5)"

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
、要用内存可视性规则来保障多线程中资源的共享问题,而不应该依赖硬件或者编译器选项

阅读(1423) | 评论(0) | 转发(0) |
0

上一篇:POSIX 线程详解

下一篇:pthread之memory barries

给主人留下些什么吧!~~