【linux 多线程并发】线程本地数据存储的两种方式,每个线程可以有同名全局私有数据,以及两种方式的性能分析_linux线程本地存储(2)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

TLS方式

在linux 系统下一般有两种方式来定义线程本地变量,这一技术叫做Thread Local Storage, TLS。

  • GCC的__thread关键字
  • 键值对API

TLS生命周期

线程本地变量的生命周期与线程的生命周期一样,当线程结束时,线程本地变量的内存就会被回收。

当然这里需要特别注意,当线程本地变量为指针类型时,动态分配的内存空间,系统并不会自动回收,只是将指针变量置为NULL,为了避免内存泄漏,需要在线程退出时主动进行清理动作,这将在后面的博文中介绍。

线程pthread结构内存

在介绍线程本地变量存储时,就不得不介绍一下pthread结构的内存,它定义了线程的重要数据结构,描述了用户状态线程的完整信息。

pthread 结构非常复杂,通过 specific_1stblock 数组和特定的辅助数组与 TLS 相关。

#define PTHREAD\_KEY\_2NDLEVEL\_SIZE 32
#define PTHREAD\_KEY\_1STLEVEL\_SIZE \
 ((PTHREAD\_KEYS\_MAX + PTHREAD\_KEY\_2NDLEVEL\_SIZE - 1) \
 / PTHREAD\_KEY\_2NDLEVEL\_SIZE)

struct pthread
{
    union
  {
#if !TLS\_DTV\_AT\_TP
    /\* This overlaps the TCB as used for TLS without threads (see tls.h). \*/
    tcbhead\_t header;
#else
    struct
    {
      int multiple_threads;
      int gscope_flag;
    } header;
#endif

    void \*__padding[24];
  };

  list\_t list;
  pid\_t tid;

  ...
  struct pthread\_key\_data
  {
    /\* Sequence number. We use uintptr\_t to not require padding on
 32- and 64-bit machines. On 64-bit machines it helps to avoid
 wrapping, too. \*/
    uintptr\_t seq;

    /\* Data pointer. \*/
    void \*data;
  } specific_1stblock[PTHREAD_KEY_2NDLEVEL_SIZE];

  /\* Two-level array for the thread-specific data. \*/
  struct pthread\_key\_data \*specific[PTHREAD_KEY_1STLEVEL_SIZE];

  /\* Flag which is set when specific data is set. \*/
  bool specific_used;
  ...
}

__thread 关键字

该关键字可用于在 GCC/Clang 编译环境中声明 TLS 变量, 该关键字不是 C 标准,并且因编译器不同而有差异;

原理介绍

使用 __thread关键字声明的变量存储在线程的pthred 结构与堆栈空间之间,也就是说,在内存布局方面,从高地址到底层地址的内存分布为:pthred结构、可变区和堆栈区(堆栈的底部和可变区的顶部是连续的);

在这种方式下的线程本地变量,变量的类型不能是复杂的类型,如C++的class类型,而且动态申请的变量空间,需要主动释放,线程结束时,只是对变量空间回收,而对应的动态内存则会泄漏。

代码举例

/\* 
 \* created by senllang 2024/1/1 
 \* mail : study@senllang.onaliyun.com 
 \* Copyright (C) 2023-2024, senllang
 \*/
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define THREAD\_NAME\_LEN 32
__thread char threadName[THREAD_NAME_LEN];
__thread int delay = 0;

typedef struct ThreadData 
{
    char name[THREAD_NAME_LEN];
    int delay;
}ThreadData;

void \*threadEntry(void \*arg) 
{
    int ret = 0;
    int i = 0;
    ThreadData \* data = (ThreadData \*)arg;

    printf("[%lu] thread entered \n", pthread\_self());

    strncpy(threadName, data->name, THREAD_NAME_LEN);
    delay = data->delay;

    for(i = 0; i < delay; i++)
    {
        usleep(10);
    }
    printf("[%lu] %s exiting after delay %d.\n", pthread\_self(), threadName, delay);
    pthread\_exit(&ret);
}

int main(int argc, char \*argv[]) 
{
    pthread\_t thid1,thid2,thid3;
    void \*ret;
    ThreadData args1 = {"thread 1", 50000}, args2 = {"thread 2", 25000}, args3 = {"thread 3", 12500};

    strncpy(threadName, "Main Thread", THREAD_NAME_LEN);

    if (pthread\_create(&thid1, NULL, threadEntry, &args1) != 0) 
    {
        perror("pthread\_create() error");
        exit(1);
    }

    if (pthread\_create(&thid2, NULL, threadEntry, &args2) != 0) 
    {
        perror("pthread\_create() error");
        exit(1);
    }

    if (pthread\_create(&thid3, NULL, threadEntry, &args3) != 0) 
    {
        perror("pthread\_create() error");
        exit(1);
    }

    if (pthread\_join(thid1, &ret) != 0) 
    {
        perror("pthread\_create() error");
        exit(3);
    }

    if (pthread\_join(thid2, &ret) != 0) 
    {
        perror("pthread\_create() error");
        exit(3);
    }

    if (pthread\_join(thid3, &ret) != 0) 
    {
        perror("pthread\_create() error");
        exit(3);
    }

    printf("[%s]all thread exited delay:%d .\n", threadName, delay);
}

每个线程定义了两个线程本地变量 threadName, delay,在线程处理函数中,对它们赋值后,再延迟一段时间,然后输出这两个变量值,结果可以看到每个线程的本地变量值都不一样,可以独立使用。

运行结果:

[senllang@hatch example_04]$ gcc -lpthread threadLocalStorage_gcc.c 
[senllang@hatch example_04]$ ./a.out 
[139945977145088] thread entered 
[139945960359680] thread entered 
[139945968752384] thread entered 
[139945960359680] thread 3 exiting after delay 12500.
[139945968752384] thread 2 exiting after delay 25000.
[139945977145088] thread 1 exiting after delay 50000.
[Main Thread]all thread exited delay:0 .

线程API方式

另一种使用线程本地变量的方式,是使用线程key相关的API,它分为两类,一是创建和销毁接口,另一类是变量的设置与获取接口。

这种方式下,线程的本地数据存储在 pthread结构中,其中specific_1stblock,specific两个数组按key值索引,并存储对应的线程本地数据;

线程本地数据的数量,在这种方式下是有限的。

创建与销毁接口

#include <pthread.h> 
int pthread\_key\_create(pthread\_key\_t \*key, void(\*destructor)(void\*));
int pthread\_key\_delete(pthread\_key\_t key);

创建接口,获取一个 pthread_key_t变量的值,其实就是内存获取一个键值来存储数据,第二个参数destructor传递一个销毁数据的方法,当本地数据为复杂数据类型,或者动态申请内存时,在线程退出时进行清理调用。

在线程使用完后,需要释放对应的key。

设置本地变量值接口

#include <pthread.h> 
int pthread\_setspecific(pthread\_key\_t key, const void \* value);
void \* pthread\_getspecific(pthread\_key\_t key);

这里设置线程的本地变量值,和获取线程本地变量值;

在不同线程中设置时,就会只设置当前线程的本地变量,不影响其它线程。

代码示例

/\* 
 \* created by senllang 2024/1/1 
 \* mail : study@senllang.onaliyun.com 
 \* Copyright (C) 2023-2024, senllang
 \*/

#include <stdio.h> 
#include <pthread.h> 
  
// 定义一个 TLS 键 
pthread\_key\_t tls_key;  

void ShowThreadLocalData(char \*prompt, pthread\_t thid)
{
    // 获取 TLS 存储的值 
    int \*value = (int \*) pthread\_getspecific(tls_key);  
    if (value == NULL) 
    {  
        printf("[%s]Thread: %ld, Value: NULL\n", prompt, thid);  
    } else 


### 最后的话

最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!

### 资料预览

给大家整理的视频资料:

![](https://img-blog.csdnimg.cn/img_convert/cfef2f2f687a4b5f87b82b9cfe81c1f3.png)

给大家整理的电子书资料:

  

![](https://img-blog.csdnimg.cn/img_convert/41200bd9972c193517565f88dfec6a71.png)



**如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!**

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.youkuaiyun.com/forums/4f45ff00ff254613a03fab5e56a57acb)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**




**如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!**

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.youkuaiyun.com/forums/4f45ff00ff254613a03fab5e56a57acb)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值