_Thread_local数据

-- TOC --

C11标准增加了对TLS(Thread Local Store)数据的支持。(C++也是从C++11开始支持thread_local申明)

虽然C11标准定义的thread local,但是gcc编译的时候,貌似什么都不管...

C语言用_Thread_local申明,C++使用thread_local申明。

通过代码分析,_Thread_local数据应该是距离stack比较远的一个thread独立的stack区域。

下面是测试代码,用_Thread_local申明一个thread local变量,在线程中,不断地对这个变量最加法,将最后的结果作为线程的返回值,在主线程中判断这个值是否符合预期。代码可以设置线程的数量,测试的时候,可以设置1K以上来观察。

$ cat test_thread_local.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/wait.h>
#include <pthread.h>


#define _PEXIT \
    do {\
        char ebuf[64] = {0};\
        sprintf(ebuf, "%s: %d", __FILE__, __LINE__);\
        perror(ebuf);\
        exit(errno);\
    }while(0)


int gTest = 1;
_Thread_local int tvar = 0;


void add2(void) {
    ++tvar;
    ++tvar;
}


void increment1(void) {
    printf("tvar %p\n", &tvar);
    ++tvar;
    ++tvar;
    add2();
    ++tvar;
    ++tvar;
    add2();
    pthread_exit(&tvar);
}


int main(int argc, char **argv) {
    /* thread number */
    int tnum;
    if (argc == 2)
        tnum = atoi(argv[1]);
    else
        tnum = 4;  /* default thread number */

    printf("stack %p\n", &tnum);
    printf(".data %p\n", &gTest);

    /* create threads */
    pthread_t *tid = (pthread_t*)malloc(sizeof(pthread_t)*tnum);
    if (tid == NULL) {
        fprintf(stderr, "malloc failed\n");
        return 1;
    }
    pthread_t tmp_tid;
    int i;
    for (i=0; i<tnum; ++i) {
        if (pthread_create(&tmp_tid, NULL, (void*)increment1, NULL) != 0)
            _PEXIT;
        tid[i] = tmp_tid;
    }

    /* wait all */
    void *pretval = NULL;
    for (i=0; i<tnum; ++i) {
        pthread_join(tid[i], &pretval);
        if (*(int*)pretval == 8)
            printf("ok %p\n", (int*)pretval);
        else {
            printf("error %d\n", *(int*)pretval);
            goto end;
        }
    }

end:
    /* free mem */
    free(tid);
    return 0;
}

以上测试代码的运行情况符合预期:

$ gcc -Wall -Wextra test_thread_local.c -o thread_local -lpthread
$ ./thread_local
stack 0x7ffe35e48cdc
.data 0x40407c
tvar 0x7f234f90363c
tvar 0x7f234f10263c
ok 0x7f234f90363c
tvar 0x7f234e0e063c
ok 0x7f234f10263c
tvar 0x7f234e90163c
ok 0x7f234e90163c
ok 0x7f234e0e063c

不会出现error的打印。

_Thread_local在全局范围内申明,让线程执行序列经过的函数都能够访问到。

从上面的打印输出来分析,线程的thread local存放的地址,应该是线程自己的一个存储区域。这个区域应该与线程自己的stack在一起,而且这个地址距离主线程的stack非常远,几乎不可能出现覆盖的可能。这个区域相互之间的距离,也足够大。远比很多嵌入式开发环境的stack要大很多。

虽然测试成功,但始终有个顾虑:线程的exit code返回后,这个值的存放地址既不是stack,也不是.data,而是线程自己的一块区域;进程在长时间运行过程中,可能会创建数量非常多的线程,他们生生死死循环往复;那么,后来的线程所用的虚拟地址空间,是否会覆盖前面线程已经用过的地址空间?如果是,我觉得应该是,那么线程的exit code就会被覆盖;这是否意味着,要及时处理线程的exit code,或者将其保存到另外的存储空间去,它不会一直在那里放着,它有可能在进程长时间运行过程中消失......

thread local的含义,本就是在线程开始时创建,在线程结束时收回。它主要服务于线程的运行过程中。线程结束返回的exit code,只是借用了线程运行所用的那块地址,既然线程都结束了,它的exit code,还是尽快处理吧。而且,很多时候,我们并不关心线程的exit code!

Windows MSVC编译器设置thread local的方法

使用__declspec(thread)申明:

__declspec(thread) long long IdealandThreadId=-1;
__declspec(thread) IdealandThreadInfo* pIdealandThread=NULL;

本文链接:https://cs.pynote.net/sf/c/202208241/

-- EOF --

-- MORE --