4. 线程私有数据

(1)线程私有数据的作用

  ①线程私有数据是存储和查询与某个线程相关的数据的一种机制,它希望每个线程可以独立的访问数据副本,而不需要担心与其他线程的同步访问问题

  ②进程中的所有线程都可以访问进程的整个地址空间(如全局变量),从而造成线程间使用数据的混乱,而线程私有数据可以维护基于每个线程的数据,提高数据的独立性。如每个线程都有一个errno,在线程出现之前,它是被定义在进程环境中全局可访问的整数,但由于多线程的出现,errno被重新定义为线程私有数据。这样一个线程做了设置errno操作并不会影响进程中其他线程的errno的值。

(2)线程私有数据的使用

  ①创建与线程私有数据关联的键

头文件

#include <pthread.h>

函数

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

返回值

成功返回0,否则返回错误编号

参数

(1)keyp:键存放的内存地址,这个键可以被进程中所有线程使用。但每个线程把这个键与不同的线程私有数据地址进行关联。

(2)destructor:析构函数,用于清理被关联的私有数据。被设置为NULL表示没有析构函数与键关键。

备注

(1)线程调用pthread_exit或线程执行返回时等正常退出时,析构函数会被调用。

(2)如果线程调用exit、_exit、_Exit、abort或其他异常退出时,不会调用析构函数。

(3)线程通常使用malloc为线程私有数据分配内存空间,析构函数通常释放己分配的内存。

【注意】需要确保的分配键不会由于在初始化阶段的竞争,而导致线程间多次调用pthread_key_create。代码如下见《编程实验》部分

  ②取消键与私有数据的关联

头文件

#include <pthread.h>

函数

int pthread_key_delete(pthread_key_t* keyp);

返回值

成功返回0,否则返回错误编号

备注

调用pthread_key_delete并不会激活与键关键的析构函数。

  ③设置和获取键关联的私有数据

头文件

#include <pthread.h>

函数

int pthread_setspecific(pthread_key_t key, const void* value); //成功返回0,否则返回错误编号

void* pthread_getspecific(pthread_key_t key);//返回线程私有数据,若没有与键关键的私有数据,则返回NULL。

返回值

成功返回0,否则返回错误编号

备注

调用pthread_key_delete并不会激活与关键的析构函数。

【编程实验】线程私有数据

//pthread_tls.c

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

/*演示如何使用线程私有数据*/

pthread_key_t key;        //创建关联键,key本身可被多个线程共享,但其关联的私有数据却为线程独享
pthread_mutex_t key_mutex = PTHREAD_MUTEX_INITIALIZER;
int key_count = 3; //表示正在使用key的线程数量,初始化为3

//必须初始化为PTHREAD_ONCE_INT。
static pthread_once_t init_done = PTHREAD_ONCE_INIT;

//定义线程私有数据类型
typedef struct
{
    pthread_t thread_id;
    char* name; //线程名称
}private_t;


//析构函数
void destructor(void* arg)
{
    private_t* pv = (private_t*)arg;
    
    printf("thread \"%s\"(0x%lx) exit and free thread private data...\n", pv->name, pv->thread_id);
    free(arg);

    //判断是否删除key
    pthread_mutex_lock(&key_mutex);

    key_count--;
    if(key_count <=0){
        //当没有线程正在使用key时,则删除之。
        if(pthread_key_delete(key) != 0){
            perror("delete key error");
        }
        printf("key deleted!\n");
    }

    pthread_mutex_unlock(&key_mutex);   
}

//线程的初始化(用于安全创建关联键)
void thread_init(void)
{
   printf("key creating...\n");
   pthread_key_create(&key, destructor);
}

//获取线程私有数据
void* get_private(void)
{
    void* value = NULL;
    
    //获取私有数据
    value = pthread_getspecific(key);
    if(value == NULL){//还未关联
        value =malloc(sizeof(private_t));
        if(value == NULL){
            perror("Allock key value");
        }

        //设置关联
        if( pthread_setspecific(key, (void*)value) !=0){
            printf("pthread_setspecific error");
        }
    }

    return value;
}

//线程函数
void* th_rtn(void* arg)
{
    pthread_once(&init_done, thread_init);//多线程中thread_init只会被调用一次,从而保证
                                          //key只会被pthread_key_create创建一次。

    private_t* value = (private_t*)get_private();
    value->thread_id = pthread_self();
    value->name = (char*)arg;

    printf("thread \"%s\"(0x%lx) starting...\n", value->name, value->thread_id);

    sleep(1);

    return (void*)NULL;
}

int main(void)
{
    pthread_t  th1, th2, th3;

    int err = 0;
    //创建3个子线程,会对全局共享的key进行操作
    if((err = pthread_create(&th1, NULL, th_rtn, "Thread 1"))!=0){
        perror("pthread create error");
    }
    if((err = pthread_create(&th2, NULL, th_rtn, "Thread 2"))!=0){
        perror("pthread create error");
    }
    if((err = pthread_create(&th3, NULL, th_rtn, "Thread 3"))!=0){
        perror("pthread create error");
    }

    //等待子线程结束
    pthread_join(th1, NULL);
    pthread_join(th2, NULL);
    pthread_join(th3, NULL);
    
    return 0;
}
/*输出结果
 key creating...
 thread "Thread 3"(0xb62ffb70) starting...
 thread "Thread 2"(0xb6d00b70) starting...
 thread "Thread 1"(0xb7701b70) starting...
 thread "Thread 3"(0xb62ffb70) exit and free thread private data...
 thread "Thread 2"(0xb6d00b70) exit and free thread private data...
 thread "Thread 1"(0xb7701b70) exit and free thread private data...
 key deleted!
 */