前言本文分享自零声教育学员学习总结,将详细介绍pthread_key的用法以及pthread_key的原理。线程私有数据TSD概念在多线程的程序中,所有的线程都可以使用和修改定义在全局的全局变量。也就是说全局变量被所有的线程共有。有没有一种办法,使得这个"全局变量",被线程独有,在线程的内部,该“全局变量”可以被线程的各个函数接口访问,但是
前言
本文分享自零声教育学员学习总结,将详细介绍pthread_key的用法以及pthread_key的原理。
线程私有数据
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-specific
data area. Each thread maintains a distinct thread-specific data
area. DESTR_FUNCTION, if non-NULL, is called with the value
associated to that key when the key is destroyed.
DESTR_FUNCTION is not called if the value associated is NULL when
the 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. Note
that zero is even. 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;
/* 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值得到数据的位置再返回其内容的
相关视频推荐
C++异常处理机制-4个问题开始聊try/catch的实现?
学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂
需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享
一图诠释底层数据结构的关联
跑个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 3
pthread_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个线程id
pthread_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);
}
创业项目群,学习操作 18个小项目,添加 微信:niuben22 备注:小项目!
如若转载,请注明出处:https://www.35chat.cn/45199.html