linux线程特有数据详解

  线程特定数据,也被称为线程私有数据,是一种存储和查找一个特定线程相关数据的机制。我们称这个数据为线程特定或线程私有的原因,是因为每个线程访问它自己独立的数据拷贝,而不用担心和其它线程的访问的同步。线程特定数据看似很复杂,其实我们可以把它理解为就是一个索引和指针。key结构中存储的是索引,指针value则指向线程中的私有数据,通常是malloc函数返回的指针。

假设有以下函数:

复制代码
#define _GUN_SOURCE
#include<stdio.h>
#include<string.h>
#include<errno.h>
#define MAX_ERROR_LEN 256
static char buf[MAX_ERROR_LEN];
//mystrerror返回错误码errno表示的字符串
char *mystrerror(int err)
{
    if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL)
    {
        snprintf(buf, MAX_ERROR_LEN, "Unknow error %d", err);
    }
    else
    {
        strncpy(buf, _sys__errlist[err], MAX_ERROR_LEN - 1);
        buf[MAX_ERROR_LEN - 1] = '\0';
    }

    return buf;
}
复制代码

可以看到mystrerror函数返回由错误码errno表示的字符串,而该字符串的内存来源于一块全局的静态内存块。我们知道全局静态内存块是进程内所有线程可以共同修改即访问的,由于线程的并发性,可能会导致某个线程正在用着这块内存的时候,被另一个线程给修改了,从而导致数据产生混乱,我们通过实例来感受一下,假设有两个线程使用mystrerror函数:

复制代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<pthread.h>
#include<errno.h>
#define handle_error_en(en, msg)\
    do {errno = en;    perror(msg); exit(EXIT_FAILURE);} while(0);
extern char *mystrerror(int err);
static void *threadFunc(void *arg)
{
    char *str;
    printf("Other thread about call mystrerror()\n");
    str = mystrerror(EPERM);
    printf("Other thread: str (%p) = %s\n", str, str);
    return NULL;
}
int main()
{
    pthread_t t;
    int s;
    char *str;
    str = mystrerror(EINVAL);
    printf("Main thread has called mystrerror()\n");
    s = pthread_create(&t, NULL, threadFunc, NULL);
    if (0 != s)
    {
        handle_error_en(s, "pthread_create");
    }
    s = pthread_join(t, NULL);
    if (0 != s)
    {
        handle_error_en(s, "pthread_join");
    }
    printf("Main thread: str (%p) = %s\n", str, str);
    exit(EXIT_SUCCESS);
}
复制代码

运行结果:

从上面的演示我们看到,主线程和子线程获取到的都是错误码EPERM的字符串信息。这种结果明显不是我们想要的,我们希望主线程获取的是EINVAL对应的字符串信息,子线程获取到的是EPERM对应的字符串信息。

解决以上问题的其中一种方法,就是使用线程特有数据;所谓的线程特有数据,说白了,就是一块看起来是全局的数据概念,但是其实每个线程都有其特有的数据内容,因此每个线程都需要各自不受干扰的内存用来存放数据。线程特有数据的接口如下:

#include <pthread.h>
int pthread_key_create(pthread_key_t *key, void (*destructor)(void *));
int pthread_setspecific(pthread_key_t key, const void *value);
void *pthread_getspecific(pthread_key_t key);
int pthread_key_delete (pthread_key_t key);

函数说明:

pthread_key_create:创建一个全局唯一key,用来表示一个数据概念。

pthread_setspecific, 用于线程给某个数据概念分配内存。

pthread_getspecific, 用于线程针对某个数据概念获取其对应的内存(每个线程获取的内存是不一样的),如果函数返回NULL值说明线程还未对该数据概念分配内存

接口使用思路如下:

  1. 先用pthread_key_create创建一个全局的key,用于表示一块全局的数据概念。
  2. 每个线程在使用该数据概念时,先通过pthread_getspecific查询该线程是否为该数据概念分配了内存
  3. 如果线程未对该数据概念分配内存,使用pthread_setspecific为该数据概念分配特有内存
  4. 如果线程已对该数据概念分配内存,直接操作该内存。

由于一个数据概念对应一个key,即对于一个数据概念而言不管有多少个线程pthread_key_create仅需要被调用一次,因此pthread_key_create经常在pthread_once函数里被调用。

pthread_key_create函数中有一个参数destructor,提供了一种释放线程特有数据内存的机制,当某个线程针终止时,如果该线程针对该key分配了内存,那么destructor函数就会被调用,传递给destructor函数的参数就是该线程针对该key分配的内存指针。

修改程序

复制代码
#define _GNU_SOURCE
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
#include<pthread.h>
static pthread_once_t once = PTHREAD_ONCE_INIT;
static pthread_key_t strerrorKey;
#define handle_error_en(en, msg) \
    do { errno = en; perror(msg); exit(EXIT_FAILURE);} while(0)
#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); }while(0)
#define MAX_ERROR_LEN 256

//线程特有数据的析构函数
static void destructor(void *buf)
{
    free(buf);
}
static void createKey(void)
{
    int s;
    
    /*在pthread_once函数里创建特有数据的key
    *哪个线程先调用就哪个线程创建key
    */
    s = pthread_key_create(&strerrorKey, destructor);
    if (0 != s)
    {
        handle_error_en(s, "pthread_key_create");
    }
}

char *mystrerror(int err)
{
    int s;
    char *buf;

    //一次性初始化函数
    s = pthread_once(&once, createKey);
    if (0 != s)
    {
        handle_error_en(s, "pthread_once");
    }

    //获取线程特有数据
    buf = pthread_getspecific(strerrorKey);
    //第一次获取为NULL, 线程需要分配内存
    if (buf == NULL)
    {
        buf = malloc(MAX_ERROR_LEN);
        if (buf == NULL)
        {
            handle_error("malloc");
        }

        //设置内存特有数据内存
        s = pthread_setspecific(strerrorKey, buf);
        if (0 != s)
        {
            handle_error_en(s, "pthread_setspecific");
        }
    }

    if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL)
    {
        snprintf(buf, MAX_ERROR_LEN, "Unknown error %d ", err);
    }
    else
    {
        strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
        buf[MAX_ERROR_LEN - 1] = '\0';
    }

    return buf;
}

运行结果:

 另外,可以使用线程局部储存比这简单得多,只需在全局或静态变量加修饰符__thread

例如:
static __thread int a=10; // (注意__thread的位置) extern __thread int b; // (注意__thread的位置)

带有__thread修饰符的变量,每个线程都拥有一份拷贝,且一直存在到线程终止,线程终止会自动释放这一变量。

posted @ 2020-12-10 15:06  jest549  阅读(229)  评论(0编辑  收藏  举报