Linux线程私有数据

  单线程C程序有两类基本数据:局部数据和全局数据。对于多线程C程序,添加了第三类数据:线程私有数据。线程私有数据与全局数据非常相似,区别在于前者为线程专有,对于一个线程而言,访问属性为全局的,不可跨线程。
  在单线程程序中,经常要用到"全局变量"以实现多个函数间共享数据,然而在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。
  但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问。在Linux平台下,可以使用gcc提供的关键字__thread来实现,但有一定局限性。更为通用的做法是使用POSIX线程库维护的数据结构——线程特定数据(Thread-specific-data或 TSD)。在此基础上,可以使用RAII(资源获取即初始化)的技法,进行进一步封装,简化管理和使用。

__thread关键字

  __thread是GCC内置的线程局部存储设施,__thread变量每一个线程有一份独立实体,各个线程的值互不干扰。可以用来修饰那些带有全局性且值可能改变,但是各线程独立不干扰的变量。__thread可以修饰的变量需具备如下条件:
  1)只能修饰POD类型(类似整型指针的标量),不能修饰class类型,因为无法自动调用构造函数和析构函数
  2)可以用于修饰全局变量,函数内的静态变量,不能修饰函数的局部变量或者class的普通成员变量
  3)__thread变量值只能初始化为编译器常量

C++ POD类型

pthread使用示例:

#include <pthread.h>
#include <cstdio>
#include <cstdlib>
#include <assert.h>
#include <stdint.h>

__thread uint64_t pkey = 0;

void run2( )
{
    FILE* fp = NULL;

    if( !pkey )
    {
        char fName[128] = "";
        sprintf( fName, "thread%lu.log", static_cast<unsigned long>( pthread_self() ) );
        fp   = fopen( fName, "w" );
        pkey = reinterpret_cast<uint64_t>( fp ); 

    }else fp = reinterpret_cast<FILE*>( pkey );

    fprintf( fp, "hello __thread 2\n" );
    return ;
}

void* run1( void* arg )
{
    FILE* fp = NULL;

    if( !pkey )
    {
        char fName[128] = "";
        sprintf( fName, "thread%lu.log", static_cast<unsigned long>( pthread_self() ) );
        fp   = fopen( fName, "w" );
        pkey = reinterpret_cast<uint64_t>( fp ); 

    }else fp = reinterpret_cast<FILE*>( pkey );

    fprintf( fp, "hello __thread 1\n" );

    run2();

    return NULL;
}

int main(int argc, char const *argv[])
{
    char fName[128] = "";
    sprintf( fName, "thread%lu.log", static_cast<unsigned long>( pthread_self() ) );
    FILE* fp = fopen( fName, "w" );
    pkey = reinterpret_cast<uint64_t>( fp );
    fprintf( fp, "hello __thread\n" );

    pthread_t threads[2];
    pthread_create( &threads[0], NULL, run1, NULL );
    pthread_create( &threads[1], NULL, run1, NULL );
    pthread_join( threads[0], NULL );
    pthread_join( threads[1], NULL );
    return 0;
}

线程特定数据TSD

进程中的key结构数组

  __thread关键字使用简单,但依赖于gcc编译器,且作用的变量具有局限性。更为通用的是使用POSIX提供的线程特定数据。
在这里插入图片描述

线程中的pthread结构数组

  POSIX要求实现POSIX的系统为每个进程维护一个称之为key的结构数组,这个数组中的每个结构称之为一个线程特定数据元素。
  POSIX规定系统实现的key结构数组必须包含不少于128个线程特定元素,而每个线程特定数据元素至少包含两项内容:使用标志和析构函数指针。key结构中的标志指示这个数组元素是否使用,析构函数用于线程结束以后的一些后期后期处理工作,如对所申请的内存空间的释放。
在这里插入图片描述

  除了进程范围内地的key结构数组外,系统还在进程中维护关于每个线程的线程结构,把这个特定于线程的结构称为pthread结构。
  它的部分内容是与key数组对应的指针数组,它的128个指针和进程中key数组的128个索引是逐一关联的。而指针指向的内存就是线程特定数据。

申请TSD的原理流程

在这里插入图片描述

	进程中某个线程,如线程1,要申请线程私有数据。
	1)首先在进程key结构数组中找到第一个未用的元素,并把它的的索引(0-127),返回给调用者,
		假设返回的索引是1
	2)key数组和pthread数组的索引是相互关联的。得到key数组的索引值1后,
		便可获得获得本线程的pkey[1]值,返回的是一个空指针ptr
	3)根据实际情况分配一快内存,将特定数据的指针指向刚才分配到内存区域,
		该内存区域中的数据便是线程特定数据
	4)在线程其他地方需要使用线程特定数据时,只需要通过key值,
		得到pkey数组中对应的指针
	5)线程结束时,会自动调用key结构数组中设定的析构函数,
		执行预先设定的操作,如释放第三步中分配的内存
	
	TSD使用过程中所用的系统调用如下:
TSD使用接口:

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));
//pthread_once 使用once_control参数指向的变量中的值确保init参数所指的函数在进程范围内之被调用一次
//onceptr必须是一个非本地变量(即全局变量或者静态变量),而且必须初始化为PTHREAD_ONCE_INIT
TSD使用示例:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

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);
    printf("destructor ...\n");
}
 
//初始化函数, 将对key的初始化放入该函数中,
//可以保证inti_routine函数只运行一次
void init_routine()
{
    pthread_key_create(&key, destructor_function);
    printf("init...\n");
}
 
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;
}

TSD的RAII技法封装

  RAII是Resource Acquisition Is Initialization(资源获取即初始化)的简称,是C++语言的一种管理资源、避免泄漏的惯用法。利用的就是C++构造的对象最终会被销毁的原则。
  RAII的做法是使用一个对象,在其构造时获取对应的资源,在对象生命期内控制对资源的访问,使之始终保持有效,最后在对象析构的时候,释放构造时获取的资源。
  对 pthread_key_t 进行RAII 的封装,定义ThreadLocal 类,可以使用更加安全。
RAII

TSD的RAII封装:

#include <pthread.h>
#include <boost/noncopyable.hpp>    
#include <boost/checked_delete.hpp> 
#include <cstdio>
#include <cstdlib>
#include <string>
#include <stdexcept>

template<typename T>
class ThreadLocal : public boost::noncopyable
{
    public:
    typedef ThreadLocal<T>* pThreadLocal;
    ThreadLocal()
    { pthread_key_create( &pkey_, &ThreadLocal::destroy ); }

    ~ThreadLocal()
    { pthread_key_delete( pkey_ ); }

    T& value()
    {
        T* pvalue = reinterpret_cast<T*>( pthread_getspecific( pkey_ ) );
        if( !pvalue )
        {
            T* obj = new T();
            pthread_setspecific( pkey_, reinterpret_cast<void*>( obj ) );
            pvalue = obj;
        }
        return *pvalue;
    }

    private:
    static void destroy( void* arg )
    { 
        T* obj = reinterpret_cast<T*>( arg );
        boost::checked_delete( obj );
    }

    pthread_key_t pkey_;
};

class Logger
{
    public:
    Logger()
    {
        char fName[128] = "";
        sprintf(  fName, "log_%lu.log", static_cast<unsigned long>( pthread_self() ) );
        fp = fopen( fName, "w" );
        if( !fp ) throw std::runtime_error( std::string("can not create ") + fName );
    }

    ~Logger() { fclose( fp ); }

    void log( const std::string& s ) { fprintf( fp, "%s\n", s.c_str() ); }

    private:
    FILE* fp;
};

void* run( void* arg )
{
    auto ptllogger  = reinterpret_cast< ThreadLocal<Logger>::pThreadLocal>( arg);
    Logger& plogger = ptllogger->value();
    plogger.log( "Hello thread local" );
}

int main()
{
    ThreadLocal<Logger>::pThreadLocal p = new ThreadLocal<Logger>;
    Logger& plogger = p->value();
    plogger.log( "Hello thread local" );

    pthread_t threads[2] = {0};
    pthread_create( &threads[0], NULL, run, reinterpret_cast<void*>( p ) );
    pthread_create( &threads[1], NULL, run, reinterpret_cast<void*>( p ) );
    pthread_join( threads[0], NULL );
    pthread_join( threads[1], NULL );
    delete p;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值