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

全部博文(73)

文章存档

2011年(1)

2009年(72)

我的朋友

分类: LINUX

2009-04-23 16:24:05

pthreadmemory barries

     memory barrier(内存屏障)是为解决内存可视性以及内存排序相关问题提出,是pthread使的一套内存机制,操作系统也可能使用这种机制,但 pthreadOS在这点上是没有交集,互相独立的。此外memory barrier其实就是保证几个代码块之间的同步执行。在pthreadmutex_lock,  mutex_unlock可以看作是就是pthread内存屏障机制的外在表现。


    
上有这样一句话:为编写可移植性代码,总要使用pthread内存可视化规则来保证正确性,而不能依赖于特定硬件和编译器相关的假设。这说明,内存屏障的正确要基于一种规则来进行,就可以保证。
   
   
首先来看看传说中的内存可视化规则是什么:

  1. 当线程调用pthread_create时,它所能看到的内存值也是它建立的线程能够看到的。在pthread_create调用之后向内存写入的数据,可能不会被建立的线程看到,即使写操作发生在启动新线程之前。
  2. 当线程解锁互斥量时看到的内存中的数据,同样也能被后来直接锁住相同互斥量的线程(或能过等待条件变量锁住的线程)看到。同样,在解锁互斥量之后写入的数据不必被其它线程看见,即使写操作发生在其它线程锁互斥量之前。
  3. 线程终止(或者取消,或从启动函数中返回,或调用pthread_exit)时看到的内存数据,同样能够被连接该线程的其它线程(通过调用pthread_join)看到。当然,终止后写入的数据不会被连接线程看到,即使写操作发生在连接之前。
  4. 线程发信号或者广播条件变量时看到的内存数据,同样可以被唤醒的其他线程看到。而在发信号或广播之后写入的数据不会被唤醒的线程看到,即使写操作发生在线程被唤醒之前。

我个人觉得,上面的四条是多线程编程的精华中的精华,难懂中最难懂的部分。

   
为了理解相关的内容,我们说一下内存排序相关的概念:

  1. 内存排序的概念与高速缓存、内存总线工作原理有关。
  2. 在不保证读写顺序的系统中(现在的很多计算机),高速缓存中的数据块可能在任何处理器认为方便的时候写入。
  3. 如果两个处理器向同一个内存单元写入数据,那么它们的值最初都被存储在各自的高速缓存中。最终两个值在随机的时刻,以随机的顺序写入。
  4. 即使是同一个处理器中的两个写操作,也不需要在内存中表现相同的顺序。
  5. 内存控制器可能会发现以相反的顺序写入更快或更方便。
  6. 内存总线的工作原理,也导致内存的读写可能不是原子的。
  7. 内存屏障是内存控制器不能删除、不可逾越、必须执行的一种对指令排序的命令,其本质上保证内存规则逻辑。

7条的内容解决了前6条带来的内存同步的问题与困难,同时也是多线程内存同步问题解决的基础。

以上这些内容都说明:
   
为编写可移植性代码,总要使用pthread内存可视化规则来保证正确性,而不能依赖于特定硬件和编译器相关的假设。这说明,内存屏障的正确要基于一种规则来进行,就可以保证。

例子包含三个文件:main.h,main.c thread.c,分别用于数据结构,执行体,子线程体的定义。

先看看main.h,注意main.h中的数据结构的声名,没有在int a;前面加volatile关键字,但是多了mutex的定义。用来说明,mutex是内存屏障的内在表现。

#ifndef MAIN_H
#define MAIN_H

#include
#include
#include
#include
#include
#include
#include

typedef struct test
{
        int a;
        int b;
        pthread_mutex_t mutex;
}Test;

#endif

再来看看main.c,这个文件没有什么好讲的,和大多的多线程应用没有什么区别.要主意的地方是全局变量以及互斥量的使用,
对内存屏障的理解最好与<Pthreadgcc优化>一文一起比较,会能较好的理解。
#include "main.h"
/*
请结合thread_vol来研究memory barrier的效果*/

extern  void * change( void * arg );

Test x = { 1, 0, PTHREAD_MUTEX_INITIALIZER };

int main()
{
        pthread_t pid;
        if ( pthread_create( &pid, 0, change, 0 ) )
        {
               printf( "Create thread error\n" );
               exit( -1 );
        }

        //
注意可视性规则1
        pthread_mutex_lock( &x.mutex );              
        int c = x.a;
        pthread_mutex_unlock( &x.mutex );
        while( c )
        {
               //
可视性规则2
               pthread_mutex_lock( &x.mutex );              
               c = x.a;        //
正确得到a值,才能退出循环
               pthread_mutex_unlock( &x.mutex );
        }

       printf( "%d\n", x.b );    //
理论上该值不一定正确的打印,事实上我测试了半天,都正常打印了..

        //
可视性规则3
        pthread_join( pid, 0 );

        printf( "%d\n", x.b );    //
该值正确的打印

        return 0;
}

再看看thread.c,它主要将x.a设置为0,以被主线程结束循环,x.a设置为0有内存屏障的保证,从而能正确的被主线程得到。
x.b
只有在join之后才可以确定得到,因为x.b并不在mutex的保护之下。
#include "main.h"

extern Test x;

void * change( void * arg )
{
        pthread_mutex_lock( &x.mutex );
        x.a = 0;
        pthread_mutex_unlock( &x.mutex );
        x.b = 11;

        printf( "XXXX\n" );

        return (void *)0;
}

该文只是为介绍多线程中比较难懂的内容,对于上面的大量使用加锁、解锁方法并不一定处处适用。当然上面的方法

 

阅读(1121) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~