线程的一次性初始化及特有数据

 

一次性初始化:

假设有以下需求,在我们的多线程程序中,我们几乎同时创建了N个线程(我们无法知道哪个线程会被先执行),我们期望这N个线程中谁先运行谁就要去负责去调用一个全局的初始化函数做相关的初始化动作,而该初始化函数仅能被调用一次。

 

线程的一次性初始化函数正是为了解决上述问题而存在的,函数接口如下

#include <pthread.h>
int pthread_once(pthread_once_t *once_control, void (*init)(void));

函数说明:该函数用于保证无论有多少个线程调用该函数,也仅会执行依次由init指向的函数实现。

 

函数参数:

once_control(输入参数):指针,指向通过以下方式初始化的全局变量pthread_once_t once_var = PTHREAD_ONCE_INIT;

init(输入参数):函数指针,执行仅能被执行一次的初始化函数。

 

返回值:成功返回0,失败返回错误编码。

 

特有数据:

假设有以下函数:

/*
*mystrerror.c
*/
#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函数:

/*
*filename:main.c
*desp: Calling mystrerror() from two different threads
*/
#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);

 

函数说明:

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分配的内存指针。

 

修改mystrerror.c

/*
*mystrerror.c
*/
#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;
}

 

运行结果:

posted @ 2019-12-03 10:54  王清河  阅读(496)  评论(0编辑  收藏  举报