Linux多线程实践(4) --线程特定数据

简介: 线程特定数据int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *));int pthr...

线程特定数据

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

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

pthread_once_t once_control = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));

   在单线程程序中,我们经常要用到"全局变量"以实现多个函数间共享数据, 然而在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问。POSIX线程库通过维护一定的数据结构来解决这个问题,这个些数据称为(Thread-specific-data或 TSD), 线程特定数据如下图所示:

 

 

   从上图可知:当调用pthread_key_create 后会产生一个所有线程都可见的线程特定数据(TSD)的键值(如上图中所有的线程都会得到一个pkey[1]的值), 但是这个键所指向的真实数据却是不同的,虽然都是pkey[1], 但是他们并不是指向同一块内存,而是指向了只属于自己的实际数据, 因此, 如果线程0更改了pkey[1]所指向的数据, 而并不能够影像到线程n;

   在线程调用pthread_setspecific后会将每个线程的特定数据与thread_key_t绑定起来,虽然只有一个pthread_key_t,但每个线程的特定数据是独立的内存空间,当线程退出时会执行destructor 函数。

/** 示例1: 设置/获取线程特定数据
在两个线程中分别设置/获取线程特定数据, 查看两个线程中的数据是否是一样的(肯定是不一样的O(∩_∩)O~)
**/
pthread_key_t key;
typedef struct Tsd
{
    pthread_t tid;
    char *str;
} tsd_t;
//用来销毁每个线程所指向的实际数据
void destructor_function(void *value)
{
    free(value);
    cout << "destructor ..." << endl;
}

void *thread_routine(void *args)
{
    //设置线程特定数据
    tsd_t *value = (tsd_t *)malloc(sizeof(tsd_t));
    value->tid = pthread_self();
    value->str = (char *)args;
    pthread_setspecific(key, value);
    printf("%s setspecific, address: %p\n", (char *)args, value);

    //获取线程特定数据
    value = (tsd_t *)pthread_getspecific(key);
    printf("tid: 0x%x, str = %s\n", (unsigned int)value->tid, value->str);
    sleep(2);

    //再次获取线程特定数据
    value = (tsd_t *)pthread_getspecific(key);
    printf("tid: 0x%x, str = %s\n", (unsigned int)value->tid, value->str);

    pthread_exit(NULL);
}

int main()
{
    //这样每个线程当中都会有一个key可用了,
    //但是每个key所绑定的实际区域需要每个线程自己指定
    pthread_key_create(&key, destructor_function);

    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, thread_routine, (void *)"thread1");
    pthread_create(&tid2, NULL, thread_routine, (void *)"thread2");

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_key_delete(key);

    return 0;
}
/** 示例2:运用pthread_once, 让key只初始化一次
注意: 将对key的初始化放入到init_routine中
**/
pthread_key_t key;
pthread_once_t once_control = PTHREAD_ONCE_INIT;
typedef struct Tsd
{
    pthread_t tid;
    char *str;
} tsd_t;

//线程特定数据销毁函数,
//用来销毁每个线程所指向的实际数据
void destructor_function(void *value)
{
    free(value);
    cout << "destructor ..." << endl;
}

//初始化函数, 将对key的初始化放入该函数中,
//可以保证inti_routine函数只运行一次
void init_routine()
{
    pthread_key_create(&key, destructor_function);
    cout << "init..." << endl;
}

void *thread_routine(void *args)
{
    pthread_once(&once_control, init_routine);

    //设置线程特定数据
    tsd_t *value = (tsd_t *)malloc(sizeof(tsd_t));
    value->tid = pthread_self();
    value->str = (char *)args;
    pthread_setspecific(key, value);
    printf("%s setspecific, address: %p\n", (char *)args, value);

    //获取线程特定数据
    value = (tsd_t *)pthread_getspecific(key);
    printf("tid: 0x%x, str = %s\n", (unsigned int)value->tid, value->str);
    sleep(2);

    //再次获取线程特定数据
    value = (tsd_t *)pthread_getspecific(key);
    printf("tid: 0x%x, str = %s\n", (unsigned int)value->tid, value->str);

    pthread_exit(NULL);
}

int main()
{
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, thread_routine, (void *)"thread1");
    pthread_create(&tid2, NULL, thread_routine, (void *)"thread2");

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_key_delete(key);

    return 0;
}


附-Linux/Unix线程私有数据实现思想:

原文连接:http://blog.csdn.net/caigen1988/article/details/7901248

   线程私有数据实现的主要思想是:在分配线程私有数据之前,创建与该数据相关联的键,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程私有数据地址进行关联,需要说明的是每个系统支持有限数量的线程特定数据元素(如:限制为128个)。那么这个键的实现原理是什么呢?

    其实系统为每个进程维护了一个称之为 Key 结构的结构数组,如下图所示:


(图1)

   在上图中Key 结构的“标志”指示这个数据元素是否正在使用。在刚开始时所有的标志初始化为“不在使用”。当一个线程调用pthread_key_create创建一个新的线程特定数据元素时,系统会搜索Key结构数组,找出第一个“不在使用”的元素。并把该元素的索引(0~127,称为“键”)返回给调用线程。

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

 

(图2)

   在上图中,pkey数组所有元素都被初始化为空指针。这些128个指针是和进程内128个可能的键逐一关联的值。

那么当我们调用pthread_key_create函数时,系统会为我们做什么呢?

    系统首先会返回给我们一个Key结构数组中第一个“未被使用”的键(即索引值),每个线程可以随后通过该键找到对应的位置,并且为这个位置存储一个值(指针)。 一般来说,这个指针通常是每个线程通过调用malloc来获得的。

知道了大概的私有数据实现的原理,那么在编程中如何使用线程的特定数据呢?

   假设一个进程被启动,并且多个线程被创建。 其中一个线程调用pthread_key_create。系统在Key结构数组(图1)中找到第1个未使用的元素。并把它的索引(0~127)返回给调用者。我们假设找到的索引为1。

之后线程调用pthread_getspecific获取本线程的pkey[1] 的值(图(2)中键1所值的指针), 返回值是一个空值,线程那么调用malloc分配内存区并初始化此内存区。 之后线程调用pthread_setspecific把对应的所创建键的线程特定数据指针(pkey[1]) 设置为指向它刚刚分配的内存区。下图指出了此时的情形。

 

(图3)

明白了怎样获取线程的特定数据值,那么如果线程终止时系统会执行什么操作呢?

   我们知道,一个线程调用pthread_key_create创建某个特定的数据元素时,所指定的参数之一便是指向析构函数的指针。当一个线程终止时,系统将扫描该线程的pkey数组,为每个非空的pkey指针调用相应的析构函数。 相应的析构函数是存放在图1中的Key数组中的函数指针。这是一个线程终止时其线程特定数据的释放手段。

目录
相关文章
|
1月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
145 0
|
6天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
3天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
6天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
16天前
|
缓存 Java 调度
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文旨在为读者提供一个关于Java多线程编程的全面指南。我们将从多线程的基本概念开始,逐步深入到Java中实现多线程的方法,包括继承Thread类、实现Runnable接口以及使用Executor框架。此外,我们还将探讨多线程编程中的常见问题和最佳实践,帮助读者在实际项目中更好地应用多线程技术。
21 3
|
18天前
|
监控 安全 Java
Java多线程编程的艺术与实践
【10月更文挑战第22天】 在现代软件开发中,多线程编程是一项不可或缺的技能。本文将深入探讨Java多线程编程的核心概念、常见问题以及最佳实践,帮助开发者掌握这一强大的工具。我们将从基础概念入手,逐步深入到高级主题,包括线程的创建与管理、同步机制、线程池的使用等。通过实际案例分析,本文旨在提供一种系统化的学习方法,使读者能够在实际项目中灵活运用多线程技术。
|
16天前
|
缓存 安全 Java
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文将深入探讨Java中的多线程编程,包括其基本原理、实现方式以及常见问题。我们将从简单的线程创建开始,逐步深入了解线程的生命周期、同步机制、并发工具类等高级主题。通过实际案例和代码示例,帮助读者掌握多线程编程的核心概念和技术,提高程序的性能和可靠性。
12 2
|
17天前
|
Java
Java中的多线程编程:从基础到实践
本文深入探讨Java多线程编程,首先介绍多线程的基本概念和重要性,接着详细讲解如何在Java中创建和管理线程,最后通过实例演示多线程的实际应用。文章旨在帮助读者理解多线程的核心原理,掌握基本的多线程操作,并能够在实际项目中灵活运用多线程技术。
|
21天前
|
Java API 调度
Java中的多线程编程:理解与实践
本文旨在为读者提供对Java多线程编程的深入理解,包括其基本概念、实现方式以及常见问题的解决方案。通过阅读本文,读者将能够掌握Java多线程编程的核心知识,提高自己在并发编程方面的技能。
|
21天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
15 3