聊聊Linux中的线程本地存储(1)——什么是TLS

从本篇开始进入另一个话题:线程本地存储(Thread Local Storage),在介绍这个概念前先说说变量和多线程的相关知识。

多线程下的变量模型

在单线程模型下,变量定义有两个维度,那就是在何处定义,以及它的修饰属性(static, extern,auto,register等)。extern属性表示声明一个变量 ,与定义无关,在此不作讨论;而register是将变量优化成寄存器里面,不作讨论。与变量定义相关的修饰属性就只有auto和static了。这两个维度可以得到变量的类型,以及它们的行为。评价一个变量的行为,通过常是从可见性
生命周期两方面评定的,下面将变量的“何处定义”和“修饰属性”,得出“变量类型”以及从“可见性”和“生命周期”列出一个完整的表格,如下:

何处定义修饰属性变量类型生命周期可见性
在函数外auto全局变量整个程序全局可见
在函数外static全局静态变量整个程序同一.c文件可见
在函数内auto局部变量函数开始执行到结束这段时间函数内可见
在函数内static静态局部变量整个程序函数内可见

对于静态局部变量会有这样的疑问,它只能在函数内使用,为什么它的生命周期是全局的?其实它与局部变量是不一样的,局部变量在函数开始执行时,才创建出来的,在函数执行结束时,它就消失了,不存在了。而静态局部变量,在函数没有执行时,它就已经在存在了,这就是为什么说它是整个程序的生命周期。当函数执行结束之后,它仍然存在,下次再执行该函数时,它是基于之前执行后的结果,开始执行的。所以它的行为与全局变量是一样的,不同之外是只能在函数内才能对它进行访问。

在单线程编程下面,这一切都协调得很好。因为整个进程只有一个执行单元,何任时刻只有一个执行代码(或者函数)在访问同一变量。

然而在多线程模式下,这一切都发生了变化。在多线程模式下面,同时出现多个执行单元,也即同一个函数会同时在多个执行单元中运行

提起多线程,几乎都想起应用加锁进行保护数据,但这不是充分必要的。

局量变量(生命周期在函数内的)是不受多线程影响,因为每个线程栈是独立的,多个线程同时执行该函数,访问局变部变量都是每线程一份的,不会产生数据竞争(race)。

但对于生命周期为整个程序的全局变量、全局静态变量和静态局部变量就不是那么一回事了。多线程下出现多个执行单元同时访问该变量,会出现数据竞争。在这种情况下,必须使用加锁进行数据保护,才能保证程序逻辑的正确性。

提起加锁,直觉反应就是将部分代码又回到串行执行的时代,如果加锁很频繁,那整个进程能并发执行的代码比例并不高,使用多线程带来的加速比并不高,最重要的是无法根据处理器个数来动态伸缩。

在多程线程序设计中,很多时候对数据进行划分,不同的数据区域交给不同的线程处理,可以避免多线程中竞争访问数据。但是有一个问题得考虑,那就是:各个独立线程在执行函数时(通常是不同线程执行相同的函数,只是他们访问的数据不同),这些函数的中间输出结果能否已是线程独立的呢?如果能完全独立,则多线程之间完全不需要互斥,伸缩性最优。

这个问题有两个解决办法,首先想到的是这些函数的输入和输出全部用参数传递。但它对编程要求很高, 有时这样的设计往往并不是最优的。很多时候函数的中间输出结果需要使用全局变量来保存(当然我们不推荐全局变量满天飞,但在一个模块内的静态全局变量还是常有的),这样就需要加锁保护,效率低下。

在这个场景下,每个线程都希望自己看到的全局变量是自己的,它也不想看到别人的,也不想别人对它有修改,它也不想修改别人的,全局变量也是个形式而已,最终目的是它可以独立拥有一份

那么要解决多线程下的高效编程问题,必须对原来的变量模型稍作修改,支持一个额外的属性,那就是变量是多执行单元(线程)共享还是独立拥有一份。

何处定义修饰属性多执行单元共享or独享变量类型生命周期可见性
在函数外auto共享全局变量整个程序全局可见
在函数外auto独享全局线程变量整个程序全局可见
在函数外static共享全局静态变量整个程序同一.c文件可见
在函数外static独享全局静态线程变量整个程序同一.c文件可见
在函数内auto独享局部变量只在函数开始执行到结束这段时间函数内
在函数内static共享静态局部变量整个程序函数内可见
在函数内static独享静态局部线程变量整个程序函数内可见

注意,局部变量本来就是线程函数内独享的,所以它没有共享这个属性值,整个程序生命周期的变量都有共享和独享这两个属性值。

什么是TLS

写到这里,TLS的定义就不言而喻。TLS全称为Thread Local Storage,即线程本地存储。在单线程模式下,所有整个程序生命周期的变量都是只有一份,那是因为只是一个执行单元;而在多线程模式下,有些变量需要支持每个线程独享一份的功能。这种每线程独享的变量放到每个线程专有的存储区域,所以称为线程本地存储(Thread Local Storage)或者线程私有数据(Thread Specific Data)。

Linux下支持两种方式定义和使用TLS变量,具体如下表:

定义方式支持层次访问方式
__thread关键字语言层面与全局变量完全一样
pthread_key_create函数运行库层面pthread_get_specific和pthread_set_specific对线程变量进行读写

应用语言支持的__thread关键字是最简单的,只须在定义变量时增加一个__thread关键字,后续对该变量的访问方式完全保持不变,所以这个是语言级别上的支持,属于隐式支持。_thread关键字是gcc对C语言的扩展,不是C语言标准定义的,当然Windows下的Visual Stdio也使用另一个关键字做扩展。

__thread例子

下面使用简单的例子说明__thread关键字的用法,以及实际的效果。

__thread int var = 0;   // var定义为线程变量,每个数线拥有一份

void* worker(void* arg);  

int main(){  
    pthread_t pid1,pid2;  

    pthread_create(&pid1,NULL,worker,(void *)0);  
    pthread_create(&pid2,NULL,worker,(void *)1);  

    pthread_join(pid1,NULL);  
    pthread_join(pid2,NULL);  

    return 0;  
}  
void* worker(void* arg){  
    int idx = (int)arg;
    int i;

    for (i = 0; i < 100; ++i) {
        printf("thread:%d  ++var = %d\n", idx, ++var); // 每个线程只访问自己独享的那份
    }
}  

使用pthread_key_create/pthread_get_specific/pthread_set_specific函数创建和使用的线程变量,在后面的会作相应的介绍,我们暂时不作讨论。

后面文章中,我们使用线程变量这一术语表示每线程独享一份的变量,而使用TLS述语来表示对这一类变量存储属性的统称。

  • 21
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
在Windows下,C语言可以通过Windows API来实现线程本地存储(Thread-Local Storage,TLS)。以下是一个简单的示例代码来演示如何在Windows下使用C语言实现线程本地存储。 ```c #include <windows.h> // 声明线程本地存储变量 __declspec(thread) int tls_variable; // 线程函数 DWORD WINAPI ThreadFunction(LPVOID lpParam) { // 设置线程本地存储变量的值 tls_variable = 42; // 在线程内部访问线程本地存储变量的值 printf("Thread local variable value: %d\n", tls_variable); return 0; } int main() { HANDLE hThread; // 创建一个线程 hThread = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL); if (hThread == NULL) { printf("Failed to create thread\n"); return 1; } // 等待线程结束 WaitForSingleObject(hThread, INFINITE); // 关闭线程句柄 CloseHandle(hThread); return 0; } ``` 在上面的示例代码,我们使用`__declspec(thread)`关键字来声明一个线程本地存储变量`tls_variable`。在`ThreadFunction`线程函数,我们设置了线程本地存储变量的值为42,并在线程内部打印了变量的值。 需要注意的是,使用`__declspec(thread)`关键字声明的线程本地存储变量只能是静态或全局变量,不能是局部变量。 编译运行上述代码后,你会看到输出显示了线程本地存储变量的值为42。这证明了在线程之间,每个线程都有自己独立的线程本地存储空间。 这只是一个简单的示例,实际应用,你可以根据需要在线程函数使用线程本地存储来存储和访问特定于每个线程数据
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值