• 线程局部存储(ACE_TSS的内部实现)


    为什么要有TLS?原因在于,进程中的全局变量与函数内定义的静态(static)变 量,是各个线程都可以访问的共享变量。在一个线程修改的内存内容,对所有线程都生效。这是一个优点也是一个缺点。说它是优点,线程的数据交换变得非常快 捷。说它是缺点,一个线程死掉了,其它线程也性命不保; 多个线程访问共享数据,需要昂贵的同步开销,也容易造成同步相关的BUG。

      如果需要在一个线程内部的各个函数调用都能访问、但其它线程不能访问的变量(被称为static memory local to a thread 线程局部静态变量),就需要新的机制来实现。这就是TLS。

      线程局部存储在不同的平台有不同的实现,可移植性不太好。幸好要实现线程局部存储并不难,最简单的办法就是建立一个全局表,通过当前线程ID去查询相应的数据,因为各个线程的ID不同,查到的数据自然也不同了。

      大多数平台都提供了线程局部存储的方法,无需要我们自己去实现:

      linux:

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

      int pthread_key_delete(pthread_key_t key);

      void *pthread_getspecific(pthread_key_t key);

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

      Win32

      方法一:每个线程创建时系统给它分配一个LPVOID指针的数组(叫做TLS数组),这个数组从C编程角度是隐藏着的不能直接访问,需要通过一些C API函数调用访问。首先定义一些DWORD线程全局变量或函数静态变量,准备作为各个线程访问自己的TLS数组的索引变量。一个线程使用TLS时,第一步在线程内调用TlsAlloc()函数,为一个TLS数组索引变量与这个线程的TLS数组的某个槽(slot)关联起来,例如获得一个索引变量:

      global_dwTLSindex=TLSAlloc();

      注意,此步之后,当前线程实际上访问的是这个TLS数组索引变量的线程内的拷贝版本。也就说,不同线程虽然看起来用的是同名的TLS数组索引变量,但实际上各个线程得到的可能是不同DWORD值。其意义在于,每个使用TLS的线程获得了一个DWORD类型的线程局部静态变量作为TLS数组的索引变量。C/C++原本没有直接定义线程局部静态变量的机制,所以在如此大费周折。

      第二步,为当前线程动态分配一块内存区域(使用LocalAlloc()函数调用),然后把指向这块内存区域的指针放入TLS数组相应的槽中(使用TlsValue()函数调用)。

      第三步,在当前线程的任何函数内,都可以通过TLS数组的索引变量,使用TlsGetValue()函数得到上一步的那块内存区域的指针,然后就可以进行内存区域的读写操作了。这就实现了在一个线程内部这个范围处处可访问的变量。

      最后,如果不再需要上述线程局部静态变量,要动态释放掉这块内存区域(使用LocalFree()函数),然后从TLS数组中放弃对应的槽(使用TlsFree()函数)。

    在多线程程序中,经常要用全局变量来实现多个函数间的数据共享。由于数据空间是共享的,因此全局变量也为所有进程共有。但有时应用程序设计中必要提供线程私有的全局变量,这个变量仅在线程中有效,但却可以跨过多个函数访问。

    比 如在程序里可能需要每个线程维护一个链表,而会使用相同的函数来操作这个链表,最简单的方法就是使用同名而不同变量地址的线程相关数据结构。这样的数据结 构可以由 Posix 线程库维护,成为线程私有数据 (Thread-specific Data,或称为 TSD)。

    这里主要测试和线程私有数据有关的 4 个函数:

    pthread_key_create();
    pthread_key_delete();

    pthread_getspecific();
    pthread_setspecific();

    程序代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    pthread_key_t key;
    struct test_struct {
        int i;
        float k;
    };
    void *child1 (void *arg)
    {
        struct test_struct struct_data;
        struct_data.i = 10;
        struct_data.k = 3.1415;
        pthread_setspecific (key, &struct_data);
        printf ("结构体struct_data的地址为 0x%p\n", &(struct_data));
        printf ("child1 中 pthread_getspecific(key)返回的指针为:0x%p\n", (struct test_struct *)pthread_getspecific(key));
        printf ("利用 pthread_getspecific(key)打印 child1 线程中与key关联的结构体中成员值:\nstruct_data.i:%d\nstruct_data.k: %f\n", ((struct test_struct *)pthread_getspecific (key))->i, ((struct test_struct *)pthread_getspecific(key))->k);
        printf ("------------------------------------------------------\n");
    }
    void *child2 (void *arg)
    {
        int temp = 20;
        sleep (2);
        printf ("child2 中变量 temp 的地址为 0x%p\n",  &temp);
        pthread_setspecific (key, &temp);
        printf ("child2 中 pthread_getspecific(key)返回的指针为:0x%p\n", (int *)pthread_getspecific(key));
        printf ("利用 pthread_getspecific(key)打印 child2 线程中与key关联的整型变量temp 值:%d\n", *((int *)pthread_getspecific(key)));
    }
    int main (void)
    {
        pthread_t tid1, tid2;
        pthread_key_create (&key, NULL);
        pthread_create (&tid1, NULL, (void *)child1, NULL);
        pthread_create (&tid2, NULL, (void *)child2, NULL);
        pthread_join (tid1, NULL);
        pthread_join (tid2, NULL);
        pthread_key_delete (key);
        return (0);
    }


    运行与输出

     ./pthread_key 
    结构体struct_data的地址为 0x0xb7699388
    child1 中 pthread_getspecific(key)返回的指针为:0x0xb7699388
    利用 pthread_getspecific(key)打印 child1 线程中与key关联的结构体中成员值:
    struct_data.i:10
    struct_data.k: 3.141500
    ------------------------------------------------------
    child2 中变量 temp 的地址为 0x0xb6e9838c
    child2 中 pthread_getspecific(key)返回的指针为:0x0xb6e9838c


    由输出可见,pthread_getspecific() 返回的是与key 相关联数据的指针。需要注意的是,在利用这个返回的指针时,它首先是 void 类型的,它虽然指向关联的数据地址处,但并不知道指向的数据类型,所以在具体使用时,要对其进行强制类型转换。
    其次,两个线程对自己的私有数据操作是互相不影响的。也就是说哦,虽然 key 是同名且全局,但访问的内存空间并不是相同的一个。key 就像是一个数据管理员,线程的私有数据只是到他那去注册,让它知道你这个数据的存在。

  • 相关阅读:
    Embedded
    uboot第一阶段详细分析
    nandflash裸机程序分析
    u-boot在nandflash中的前4k内容分析
    使用JLink间接烧写s3c6410 nand_flash的方法
    Emacs
    U-boot 软浮点 错误 解决方法
    linux系统移植流程
    使用JLink烧写bin文件到Mini2440
    uboot-2012.07移植到fl2440开发板(一)
  • 原文地址:https://www.cnblogs.com/dongzhiquan/p/2498609.html
Copyright © 2020-2023  润新知