Chinaunix首页 | 论坛 | 博客

fx

  • 博客访问: 1347053
  • 博文数量: 115
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 3964
  • 用 户 组: 普通用户
  • 注册时间: 2013-05-02 14:36
文章分类
文章存档

2022年(2)

2019年(2)

2018年(10)

2017年(1)

2016年(50)

2015年(12)

2014年(9)

2013年(29)

分类: LINUX

2013-06-13 09:37:50

私有数据的目的是希望每个线程可以独立的访问数据副本。而不需要担心与其他线程的同步访问问题。
比如全局变量 errno他是基本进程的。因为进程创建的时候,子进程会复制父进程的数据空间,那么子进程就拥有自己的全局变量errno。即使存在多个进程间的频繁的切换,也不会造成error的访问错误,因为他们都有自己的errno全局变量。
但是在多线程环境中就不行了。因为所有线程都会共享一个全局变量error。那么当线程频繁的切换时,很可能A线程判断的errno是B线程中的错误返回值。
所以要想让errno这种基于进程的接口能适应多线程环境中,那么就需要一种措施,时各个线程对errno的访问不会影响别的线程,
私有数据提供了解决这个问题的办法,errno被重新定义为线程的私有数据。这样所有的线程仍旧可以访问errno这个变量名,但其实他们真正访问的内容却是不一样的,访问的都是属于自己的私有数据。


下面是关于私有数据的一些接口。
在分配私有数据之前,需要创建于该数据关联的键,这个键用来获得对线程私有数据的访问,


int pthread_key_create(pthread_key_t *keyp,void (*destructor)(void *));


创建的键存放在keyp指向的内存单元中,这个键可以被进程中的所有线程使用,但是每个线程吧这个键与不同的线程私有数据地址进行关联。
destructor为与该键的关联的析构函数,他的参数就是私有数据的地址。如果destructor参数为NULL表明没有析构函数和键关联。
线程正常退出时会调用析构函数,非正常退出不会调用。(后面还会进一步说明)

int pthread_ket_delete(pthread_key_t *keyp);
用来取消键与线程私有数据之间的关联关系。

既然有了键与私有数据的关联性,那么所有的线程都只需要通过这个键来访问属于自己的私有数据,那么我们就需要保障这个键的唯一性。
如果像下面这个创建一个供所有线程使用的键


int first=1;  //全局变量 


。。。。
if(first==1){
first=2;
pthread_key_create(&key,destructor);
}
。。。。


这里在判断 first是不是等于一喝first修改为2之间有一个时间窗口,那么另一个线程可能会在这个窗口中也执行了一次 pthread_key_create造成了产生另外一个键,那么不同的线程之间看到的可能就不是一个键了。


所以为了确保键只被创建一次,提供了下面这个接口
pthread_once_t initflag=PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *initflag,void (*initfn)(void));

initflag参数必须是一个非本地的变量(需要是全局或静态的),而且必须初始化为PTHREAD_ONCE_INIT,如果每个线程都使用pthread_once,系统就能保证初始化历程initfn只被调用一次。

所以上面的存在竞争的创建键的方法应该改为下面的
pthread_once_t initflag=PTHREAD_ONCE_INIT;

void init_key(void){
pthread_key_create(&key,destructor);
}

void *thread_func(void *arg){
...
pthread_once(initflag,init_key);
}


这样就保证了键只会被创建一次。


最后我们需要把键与具体的私有数据关联起来。
void *pthread_getspecific(pthread_key_t key);
返回值:线程私有数据,若没有值与键相关联则返回NULL
int pthread_setspecific(pthread_key_t key,const void *value);


最后我们给出一个具体的例子。
意图是想让一个 key与两个线程 th1和th2 中各自的私有数据相关联。然后在 th1中利用pthread_getspecific得到线程 th1 与key关联的私有变量地址然后赋值给varoable(一个指针), 第一次修改 *variable=1 并在th1中打印出值。

然后切换到 th2 在 th2 中利用再次利用pthread_getspecific得到与th2 中与key相关联的私有数据地址,赋值给variable然后修改 *variable=2,并在th2 中打印。

最后我们再回到 th1中,因为variable是一个指针。那么在一般的程序中,如过使用形如 *variable=xx  就会将variable指向的
地址处的值改变。那么我们回到th1 再次调用pthread_getspecific获得 th1 与key关联的私有数据地址。然后输出。因为是私有数据
我们期望并不会像一般程序中一样会导致数据改变,他应该还是1

pthread_key_t key;

int *variable;
pthread_once_t initflag=PTHREAD_ONCE_INIT;

void init_key(void){
        int err;
        err=pthread_key_create(&key,free);
        if(err!=0){
                printf("create key error:%s\n",strerror(err));
                exit(1);
        }   
}
void thread1_private(int **thread1_private_data){
        int err;
        err=pthread_once(&initflag,init_key);
        if(err!=0){
                printf("pthread_once error:%s\n",strerror(err));
                exit(1);
        }   


        *thread1_private_data=malloc(sizeof(int));
        err=pthread_setspecific(key,*thread1_private_data);
        if(err!=0){
                printf("pthread_setspecific error:%s\n",strerror(err));
                exit(1);
        }   
     
}
void thread2_private(int **thread2_private_data){
        int err;
        err=pthread_once(&initflag,init_key);
        if(err!=0){
                printf("pthread_once error:%s\n",strerror(err));
                exit(1);
        }   


        *thread2_private_data=malloc(sizeof(int));
        err=pthread_setspecific(key,*thread2_private_data);
        if(err!=0){
                printf("pthread_setspecific error:%s\n",strerror(err));
                exit(1);
        }
}


void *th1(void *arg){
        int *pthread1_private_data;
        thread1_private(&pthread1_private_data);
        variable=pthread_getspecific(key);
        *variable=1;
        printf("in thread_1 :variable=%d\n",*variable);
        sleep(2);
        variable=pthread_getspecific(key);
        printf("in thread_1 :variable=%d\n",*variable);


        pthread_exit((void *)0);
}


void *th2(void *arg){
        int *pthread2_private_data;
        thread2_private(&pthread2_private_data);
        sleep(1);
        variable=(int *)pthread_getspecific(key);
        *variable=2;
        printf("in thread_2:variable=%d\n",*variable);
        pthread_exit((void *)0);
}
int main(){
        pthread_t tid_1,tid_2;
        int err;

        pthread_create(&tid_1,NULL,th1,(void *)0);
        pthread_create(&tid_2,NULL,th2,(void *)0);

        err=pthread_join(tid_2,NULL);
        if(err!=0){
                printf("wait thread_2 error:%s\n",strerror(err));
                exit(1);
        }

        err=pthread_join(tid_1,NULL);
        if(err!=0){
                printf("wait thread_1 error:%s\n",strerror(err));
                exit(1);
        }

        exit(0);
}
最后我们查看输出看看 th1 中variable指向的内存的值是否改变了
in thread_1 :variable=1
in thread_2:variable=2
in thread_1 :variable=1

从输出 很容易看出来 th1中variable指向的值并为收th2的影响,也就是说每个线程与共享key关联的数据都是他们是私有的!     


最后我们再来说一下与键关联的那个析构函数。前面说过该析构函数唯一的参数是私有数据的地址。如果传入的destroctor参数为NULL,表明没有析构函数和键相关联。当线程调用pthread_exit或者线程执行返回,正常退出,析构函数会被调用。但如果调用了exit,_exit,_Exit,abort或其他非正常方式退出时就不会调用析构函数
注意 ,当线程正常退出时,如果私有数据地址为NULL,那么析构函数也不会调用。
线程通常使用malloc为线程私有数据分配内存。析构函数通常用来释放分配的内存。


下面的测试 显示了析构函数的调用时间      
  4 pthread_key_t key;
  5 void destructor(void *arg){
  6         printf("in destructor\n");
  7         free(arg);
  8 }
  9 
 10 void fun(void){
 11 
 12         int *variable=NULL;
 13         variable=malloc(sizeof(int));
 14         pthread_key_create(&key,destructor);
 15         pthread_setspecific(key,variable);
 16         printf("fun done\n");
 17 }
 18 
 19 void *thread(void *arg){
 20         fun();
 21         printf("thread done\n");
 22         pthread_exit((void *)0);
 23 }
 24 
 25 int main(void){
 26         pthread_t tid;
 27         pthread_create(&tid,NULL,thread,(void *)0);
 28         pthread_join(tid,NULL);
 29         printf("main done\n");
 30         exit(0);
 31 }
程序输出如下:
fun done
thread done
in destructor
main done


从输出顺序看出,destructor的确是在线程退出的时候才调用的。
如果把  第七 和 第十三 行注释掉,也就是私有数据地址为NULL,我们再来看看析构函数还会不会调用。
输出如下:
fun done
thread done
main done
正如我们上面所说的。当线程退出时,如果私有数据的地址为NULL,那么析构函数是不会被调用的。


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