linux线程私有数据详解

简介: linux线程私有数据详解

概述

  在单线程程序中,函数经常使用全局变量或静态变量,这是不会影响程序的正确性的,但如果线程调用的函数使用全局变量或静态变量,则很可能引起编程错误,因为这些函数使用的全局变量和静态变量无法为不同的线程保存各自的值,而当同一进程内的不同线程几乎同时调用这样的函数时就可能会有问题发生。而解决这一问题的一种方式就是使用线程私有数据。线程私有数据采用了一种被称为一键多值的技术,即一个键对应多个数值。访问数据时都是通过键值来访问,好像是对一个变量进行访问,其实是在访问不同的数据。使用线程私有数据时,首先要为每个线程数据创建一个相关联的键。在各个线程内部,都使用这个公用的键来指代线程数据,但是在不同的线程中,这个键代表的数据是不同的。

设计线程私有数据的原因

  • 在维护每个线程的私有数据的时候,我们可能会想到分配一个保存线程数据的数组,用线程的ID 作为数组的索引来实现访问,但是有一个问题是系统生成的线程ID不能保证是一个小而连续的整数, 并且用数组实现的时候由于其他线程也可以访问其数组中的数据,这样会 引起数据混乱。
  • 它提供了让基于进程的接口适应多线程环境的机制.一个很明显的实例就是errno。以前的接口(线程 出现以前)把errno定义为进程上下文中全局可访问的整数。系统调用和库例程在调用或执行失败时设置 errno,把它作为操作失败的附属结果。为了让线程也能够使用那些原本基于进程的系统调用和库例程,errno 被重定义为线程私有数据。这样,一个线程做了重置errno的操作也不会影响进程中其他线程的errno值。

相关原理介绍

1.int pthread_key_create (pthread_key_t *key, void (*destructor)(void *));
//第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数,如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个
//键上的内存块。
2.int pthread_key_delete (pthread_key_t key);
3.int pthread_setspecific (pthread_key_t key, const void *value);
//其中value就是不同的线程中,key值所关联的私有数据地址,这些地址可以用malloc来分配;
4.void *pthread_getspecific (pthread_key_t key);
//获取线程私有数据的地址
5.int phread_once(pthread_once_t *onceptr, vid(*init)(void));
//注意:pthread_once 使用onceptr 参数指向的变量中的值确保init参数所指的函数在进程范围内之被调用一次,
//onceptr必须是一个非本地变量(即全局变量或者静态变量),而且必须初始化为PTHREAD_ONCE_INIT。
pthread_key_t key;
pthread_once_t once = PTHREAD_ONCE_INIT;
void destructor(void *ptr){
     free(ptr);
}
void excute_once(void)  {
    pthread_key_create(&key, destructor);
}
int main(){
   pthread_once(&r1_once, excute_once);
}

  当我们调用pthread_key_create时,内核从Linux的TSD池中分配一项,将其值赋给key供以后访问使用,它的第一个参数key为指向键值的指针,第二个参数为一个函数指针,如果指针不为空,则在线程退出时将以key所关联的数据为参数调用destr_function(),释放分配的缓冲区。 key一旦被创建,所有线程都可以访问它,但各线程可以根据自己的需要往key中填入不同的值,这就相当于提供了一个同名而不同值的全局变量,一键多值。其中TSD池的结构如下:

  内核支持的pthread_keys是有限的(可能是128也可能),除了进程范围内的Key结构数组之外,系统还在进程内维护了关于多个线程的多条信息。这些特定于线程的信息我们称之为Pthread结构。其中部分内容是我们称之为pkey数组的一个128个元素的指针数组。系统维护的关于每个线程的信息结构图如下:

  一旦我们在某个线程里面调用pthread_setspecific将key与某个空间(这个空间可以是malloc申请的)相关连的时候,上面的结构就变成

使用例子

#include <limits.h>  
#include <string.h>  
#include <pthread.h>  
#include <stdlib.h>  
#include <stdio.h>
#include<iostream>
using namespace std;
pthread_key_t key;
pthread_key_t key1;
pthread_key_t key2;
void func1(){
  int *tmp = (int*)pthread_getspecific(key);
}
void *tthread_fun(void* args){
  pthread_setspecific(key,args);
  int *tmp = (int*)pthread_getspecific(key);
  printf("线程%lu的私有地址为:%p\n",pthread_self(),tmp);
  *tmp+=1;
  func1();
  return (void*)0;    
}
int main(){
  pthread_t pa, pb;
  pthread_key_create(&key,NULL);
  pthread_key_create(&key1,NULL);
  pthread_key_create(&key2,NULL);
  cout<<key<<" "<<key1<<" "<<key2<<endl;
  pthread_t pid[3];
  int a[3]={100,200,300};
  cout<<"the address of array is: "<<&a<<endl;
  int i=0;
  for(i=0;i<3;i++){
    pthread_create(&pid[i],NULL,tthread_fun,&a[i]);
    cout<<"pthread id is: "<<pid[i]<<endl;
  }
  for(i=0;i<3;i++){
    pthread_join(pid[i],NULL);
  }
  cout<<a[0]<<" "<<a[1]<<" "<<a[2]<<endl;
  return 0;
}

输出结果:

从测试结果可以看出:

  • key的值是从0(key)开始分配的,然后是1(key1),再然后是2(key2);
  • 在进程里我们一共创建了3个进程,这三个进程共同使用一个键key(它额值为0),那么对应于每一个线程中的pkey[0]不同的线程的指向不同,就上面的程序而言,pid0的pkey[0]里存放的是变量a[0]地址,pid的pkey[0]里存放的是变量a[1]地址,pid的pkey[0]里存放的是变量a[2]地址,这三个变量分别作为这三个线程的私有数据;函数fun1的关联结构为:

  • 在不同的线程中:

int *tmp = (int*)pthread_getspecific(key);

虽然同样是key,但是我们得到的tem的地址对于每个线程来说不同,这些地址也就是图3中pkey[0]所指的地址;

目录
相关文章
|
3月前
|
消息中间件 监控 安全
服务Down机了,线程池中的数据如何保证不丢失?
在分布式系统与高并发应用开发中,服务的稳定性和数据的持久性是两个至关重要的考量点。当服务遭遇Down机时,如何确保线程池中处理的数据不丢失,是每一位开发者都需要深入思考的问题。以下,我将从几个关键方面分享如何在这种情况下保障数据的安全与完整性。
74 2
|
23天前
|
消息中间件 监控 Java
线程池关闭时未完成的任务如何保证数据的一致性?
保证线程池关闭时未完成任务的数据一致性需要综合运用多种方法和机制。通过备份与恢复、事务管理、任务状态记录与恢复、数据同步与协调、错误处理与补偿、监控与预警等手段的结合,以及结合具体业务场景进行分析和制定策略,能够最大程度地确保数据的一致性,保障系统的稳定运行和业务的顺利开展。同时,不断地优化和改进这些方法和机制,也是提高系统性能和可靠性的重要途径。
116 62
|
2月前
|
缓存 安全 Java
使用 Java 内存模型解决多线程中的数据竞争问题
【10月更文挑战第11天】在 Java 多线程编程中,数据竞争是一个常见问题。通过使用 `synchronized` 关键字、`volatile` 关键字、原子类、显式锁、避免共享可变数据、合理设计数据结构、遵循线程安全原则和使用线程池等方法,可以有效解决数据竞争问题,确保程序的正确性和稳定性。
53 2
|
2月前
|
弹性计算 Linux 数据库
阿里云国际版如何迁移Linux云服务器系统盘中的数据
阿里云国际版如何迁移Linux云服务器系统盘中的数据
|
2月前
|
资源调度 Linux 调度
Linux C/C++之线程基础
这篇文章详细介绍了Linux下C/C++线程的基本概念、创建和管理线程的方法,以及线程同步的各种机制,并通过实例代码展示了线程同步技术的应用。
33 0
Linux C/C++之线程基础
|
4月前
|
缓存 NoSQL Linux
【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据
【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据
141 1
【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据
|
2月前
|
安全 Linux
Linux线程(十一)线程互斥锁-条件变量详解
Linux线程(十一)线程互斥锁-条件变量详解
|
3月前
|
消息中间件 存储 Java
服务重启了,如何保证线程池中的数据不丢失?
【8月更文挑战第30天】为确保服务重启时线程池数据不丢失,可采用数据持久化(如数据库或文件存储)、使用可靠的任务队列(如消息队列或分布式任务队列系统)、状态监测与恢复机制,以及分布式锁等方式。这些方法能有效提高系统稳定性和可靠性,需根据具体需求选择合适方案并进行测试优化。
232 5
|
4月前
|
Linux 开发工具
linux下使用gcp拷贝数据的时候显示进度条
linux下使用gcp拷贝数据的时候显示进度条
32 2
|
4月前
|
监控 网络协议 Linux
在Linux中,如何实时抓取并显示当前系统中tcp 80 端口的网络数据信息?
在Linux中,如何实时抓取并显示当前系统中tcp 80 端口的网络数据信息?