Linux线程私有数据Thread-specific Data详解

  • 前言
  • 线程私有数据
    • TSD概念
    • Posix api细节探究
      • pthread_key_create:创建一个键
      • pthread_key_delete:注销一个键
      • pthread_setspecific:为指定key 设置线程私有数据 val
      • pthread_getspecific:从指定键读取线程的私有数据
    • 一图诠释底层数据结构的关联
  • 跑个demo看看效果

前言

  本文将详细介绍pthread_key的用法以及pthread_key的原理。pthread_key在《ntyco协程》中,以及后续文章《try catch的实现》都有用到。跟我一起学习的读者务必搞懂原理。

  本专栏知识点是通过零声教育的线上课学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接 C/C++后台高级服务器课程介绍 详细查看课程的服务。

线程私有数据

TSD概念

  在多线程的程序中,所有的线程都可以使用和修改定义在全局的全局变量。也就是说全局变量被所有的线程共有。有没有一种办法,使得这个"全局变量",被线程独有,在线程的内部,该“全局变量”可以被线程的各个函数接口访问,但是对其他线程屏蔽呢?换句话说,本文后续要介绍的,就是线程私有数据,即 表面看起来是一个“全局变量”,所有的线程都可以使用它,但是每个线程都是独享它的,它的值在每一个线程中都是单独存储的。

  线程私有数据(TSD):同名而不同值,即同key不同value,一键多值,所以对私有数据的访问都是通过键来访问到value的。

Posix api细节探究

Posix定义了两个API来创建和销毁TSD,以及两个API来设置与访问TSD

/* Functions for handling thread-specific data.  *//* Create a key value identifying a location in the thread-specificdata area.  Each thread maintains a distinct thread-specific dataarea.  DESTR_FUNCTION, if non-NULL, is called with the valueassociated to that key when the key is destroyed.DESTR_FUNCTION is not called if the value associated is NULL whenthe key is destroyed.  */
extern int pthread_key_create (pthread_key_t *__key,void (*__destr_function) (void *))__THROW __nonnull ((1));/* Destroy KEY.  */
extern int pthread_key_delete (pthread_key_t __key) __THROW;/* Return current value of the thread-specific data slot identified by KEY.  */
extern void *pthread_getspecific (pthread_key_t __key) __THROW;/* Store POINTER in the thread-specific data slot identified by KEY. */
extern int pthread_setspecific (pthread_key_t __key,const void *__pointer) __THROW ;

pthread_key_create:创建一个键

int pthread_key_create(pthread_key_t *key, void (*destr_function) (void*));

  首先从Linux的TSD池中分配一项,然后将其值赋给key供以后访问使用。接口的第一个参数是指向参数的指针,第二参数是函数指针,如果该指针不为空,那么在线程执行完毕退出时,已key指向的内容为入参调用destr_function(),释放分配的缓冲区以及其他数据。

  前面已经说了,key是全局变量,不论哪个线程调用了pthread_key_create,所创建的key都是所有的线程都可以访问,每个线程根据自己的需求往key中set不同的值,这就形成了同名而不同值,即同key不同value,一键多值。

  在Linux中,TSD池用一个结构体数组来表示,并且PTHREAD_KEYS_MAX默认为1024

cat /usr/include/bits/local_lim.h
static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] ={{0,NULL}};struct pthread_key_struct
{/* Sequence numbers.  Even numbers indicated vacant entries.  Notethat zero is even.  We use uintptr_t to not require padding on32- and 64-bit machines.  On 64-bit machines it helps to avoidwrapping, too.  */uintptr_t seq;/* Destructor for the data.  */void (*destr) (void *);
};

  创建一个TSD,相当于将结构体数组的某一个元素的seq值设置为为“in_use”,并将其索引返回给 *key,然后设置destr_function()为destr()。pthread_key_create()创建一个新的线程私有数据key时,系统会搜索进程中的这个key数组,找出一个未使用的将其索引赋值给 *key。

pthread_key_delete:注销一个键

int pthread_key_delete (pthread_key_t __key);

  这个函数不会检查当前是否有线程正在使用TSD,也不会调用清理函数destr_function,而是将TSD对应的seq置un_use,并且将相关线程对应的value置为NULL,以供下一次调用pthread_key_create使用。

pthread_setspecific:为指定key 设置线程私有数据 val

int pthread_setspecific(pthread_key_t key, const void *pointer);

  该接口将指针pointer的值(指针值而非其指向的内容)与key相关联,用pthread_setspecific为一个键指定新的线程数据时,线程必须释放原有的数据用以回收空间。

  在Linux线程中,使用一个位于 线程描述结构体(_pthread_descr_struct) 中的void **p_specific[PTHREAD_KEY_1STLEVEL_SIZE];指针数组来存放与key关联的数据val。因为PTHREAD_KEYS_MAX 为1024,所以一维数组大小为32

#define PTHREAD_KEY_2NDLEVEL_SIZE 32
#define PTHREAD_KEY_1STLEVEL_SIZE \
((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE -1) \
/ PTHREAD_KEY_2NDLEVEL_SIZE

  所以具体存放的位置由key值经过计算得到

idx1st = key/PTHREAD_KEY_2NDLEVEL_SIZE
idx2nd = key%PTHREAD_KEY_2NDLEVEL_SIZE

pthread_getspecific:从指定键读取线程的私有数据

void * pthread_getspecific(pthread_key_t key);

  读函数就是将与key关联的数据val读出来,数据类型为void * ,所有可以指向任何类型的数据。通过上面我们得知,数据存在一个32 * 32的二维数组中,所以访问的时候,也是通过计算key值得到数据的位置再返回其内容的

一图诠释底层数据结构的关联

跑个demo看看效果

  该demo开了3个线程,第一个线程key对应着一个int的整数,第二个线程key对应着字符串,第三个线程key对应着一个结构体。发现都能正常打印,并且在线程执行完毕退出时,已key指向的内容为入参调用destr_function(),释放分配的缓冲区以及其他数据。

//
// Created by 68725 on 2022/7/29.
//#include<pthread.h>
#include<stdio.h>
#include <malloc.h>
#include <memory.h>#define THREAD_COUNT 3pthread_key_t key;typedef void *(*thread_cb)(void *);void print_thread1_key(void) {int *p = (int *) pthread_getspecific(key);//将值从私有空间中取出来printf("thread 1 : %d\n", *p);
}//线程1 的回调函数
void *thread1_proc(void *arg) {int *p = (int *) malloc(sizeof(int));*p = 68725032;pthread_setspecific(key, p);//将 i传入私有空间中print_thread1_key();
}void print_thread2_key(void) {char *ptr = (char *) pthread_getspecific(key);printf("thread 2 : %s\n", ptr);
}//线程2 的回调函数
void *thread2_proc(void *arg) {char *ptr = (char *) malloc(1024 * sizeof(char));strcpy(ptr, "wxfnb");pthread_setspecific(key, ptr);print_thread2_key();
}struct pair {int x;int y;
};void print_thread3_key(void) {struct pair *p = (struct pair *) pthread_getspecific(key);printf("thread 3  x: %d, y: %d\n", p->x, p->y);
}//线程3 的回调函数
void *thread3_proc(void *arg) {struct pair *p = (struct pair *) malloc(sizeof(struct pair));p->x = 1;p->y = 2;pthread_setspecific(key, p);print_thread3_key();
}void destroy_func(void *val) {printf("free key\n");free(val);
}int main() {pthread_t th_id[THREAD_COUNT] = {0};//3个线程idpthread_key_create(&key, destroy_func);//创建key,这个全局变量可以认为是线程内部的私有空间thread_cb callback[THREAD_COUNT] = {//线程的回调函数thread1_proc,thread2_proc,thread3_proc};int i;for (i = 0; i < THREAD_COUNT; i++) {//创建线程pthread_create(&th_id[i], NULL, callback[i], NULL);}for (i = 0; i < THREAD_COUNT; i++) {pthread_join(th_id[i], NULL);//主线程需要等待子线程执行完成之后再结束}pthread_key_delete(key);
}

Linux线程私有数据Thread-specific Data(TSD) 详解相关推荐

  1. Linux系统编程——线程私有数据

    在多线程程序中.常常要用全局变量来实现多个函数间的数据共享.因为数据空间是共享的,因此全局变量也为全部线程共同拥有. 測试代码例如以下: #include <stdio.h> #inclu ...

  2. 【Linux系统编程】线程私有数据

    00. 目录 文章目录 00. 目录 01. 线程之间共享数据 02. 线程私有数据 2.1 创建线程私有数据 2.2 销毁线程私有数据 2.3 关联线程私有数据成员 2.4 读取线程私有数据所关联的 ...

  3. 【C/C++多线程编程之十】pthread线程私有数据

    多线程编程之线程私有数据 Pthread是 POSIX threads 的简称,是POSIX的线程标准.  线程同步从互斥量[C/C++多线程编程之六]pthread互斥量,信号量[C/C++多线程编 ...

  4. android字符串获取数字索引,从字符串中提取特定数据(Extract specific data from a string)...

    从字符串中提取特定数据(Extract specific data from a string) 我有一个带有描述的长字符串. 我想从字符串中提取一些信息. 但我无法弄明白该怎么做. 这是字符串: C ...

  5. [免费专栏] Android安全之数据存储与数据安全「详解」

    也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 Android安全付费专栏长期更新,本篇最新内容请前往: [ ...

  6. Linux下的tar归档及解压缩功能详解

    Linux下的tar归档及解压缩功能详解 一.Linux下解压缩工具 二.gzip工具的使用方法 三.其他解压缩工具 一.Linux下解压缩工具 二.gzip工具的使用方法 三.其他解压缩工具 一.L ...

  7. 一文搞定 Spring Data Redis 详解及实战

    转载自  一文搞定 Spring Data Redis 详解及实战 SDR - Spring Data Redis的简称. Spring Data Redis提供了从Spring应用程序轻松配置和访问 ...

  8. linux中流设备_[快速上手Linux设备驱动]之块设备驱动流程详解一

    [快速上手Linux设备驱动]之块设备驱动流程详解一 walfred已经在[快速上手Linux设备驱动]之我看字符设备驱动一 文中详细讲解了linux下字符设备驱动,并紧接着用四篇文章描述了Linux ...

  9. linux下mysql数据库基础及客户端命令详解

    linux下mysql数据库基础及客户端命令详解 1.mysql数据库存储引擎: SHOW ENGINES;   #查看mysql支持的存储引擎 常见有如下两个存储引擎: MyISAM:每表三个文件: ...

  10. Linux Kernel 6.0 CXL Core pci.c 详解

    文章目录 前言 相关链接 Ref 正文 前言 CXL 是一个比较新的技术,所以我研究的内核源码是选了当前比较新的内核版本 linux 6.0.打算将内核关于 CXL 的驱动进行解析一遍,一步一步慢慢来 ...

最新文章

  1. 内核电源管理器已启动关机转换_Linux系统启动流程
  2. UA MATH565C 随机微分方程V Markov Family简介
  3. jQueryMobile常用技巧
  4. 流式视频处理架构设计
  5. 微信小程序页面栈_微信小程序使用页面栈改变上一页面的数据
  6. ORB_SLAM : semi dense code
  7. 光线追踪技术 清华大学 pdf_实时光线追踪技术:业界发展近况与未来挑战
  8. 物联网时代,隐私还有救吗?
  9. python3 shell脚本开发_python3 subprogress 模块的使用 代替shell编写脚本
  10. This is why you don’t think you’re creative 你为什么会觉得自己没有创造力?
  11. 卡巴斯基最新Key下载 生成卡巴斯基Key的工具下载 卡巴斯基Key下载
  12. 初二生态系统思维导图_有关初中生态系统的思维导图
  13. Android抓包从未如此简单
  14. 详解低延时高音质:声音的美化与空间音效篇
  15. 关于个人目标的一篇博客
  16. 携程React Native实践
  17. Python近期练习小案例
  18. 弱电机房可视化监控综合管理系统设计方案
  19. 如何将图片放大不改变清晰度?
  20. 拼音爱好者的好消息:紫光V6发版

热门文章

  1. Python量化投资——这个均线择时投资策略,12年只交易24次,比沪深300收益率高700倍
  2. 机器学习算法工程师面试经历
  3. Jetty 安全漏洞分析
  4. 访问共享文件夹总是提示“指定的网络名不再可用”,重启就好了
  5. gitgub利用客户端实现简单的上传和同步
  6. mac教程:重置苹果Mac上的NVRAM或PRAM?
  7. devExpress各个版本的下载地址收藏
  8. ios下拉效果滑动滚出页面
  9. 一键删除PPT页面内的动画or页面的切换效果总结
  10. 泰勒展开简单直观理解与常用公式