线程局部存储空间 pthread_key_t、__thread 即 ThreadLocal

__thread

参考: 线程局部变量 __thread 关键字

  • __thread是GCC内置的线程局部存储设施,__thread变量每一个线程有一份独立实体,各个线程的值互不干扰。可以用来修饰那些带有全局性且值可能变,但是各线程独立不干扰的变量;
  • 只能修饰POD类型(类似整型指针的标量),不能修饰class类型,因为无法自动调用构造函数和析构函数;
  • 可以用于修饰全局变量,函数内的静态变量,不能修饰函数的局部变量或者class的普通成员变量;
  • 且__thread变量值只能初始化为编译器常量。
#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;
}

pthread_key_t

参考:关键字:__thread & pthread_key_t

pthread_key_t 优于 __thread 从下面几个方面来说:

  • 依赖 linux 环境的 libpthread, 而非 gcc 编译器可移植性增强
  • 如上所示,可以认为对每个 pthread_key, 库内部提供了一个 __thread void* 接受 pthread_setspecific 设置的指针,从而可以指向 class 类型
  • pthread_key_t 可以作为函数的局部变量,也可以作为局部变量。
#include <pthread.h> 
    // pthread_key_t, pthread_setspecific, pthread_getspecific, pthread_self
    // pthread_key_create, pthread_key_delete, pthread_create, pthread_join
#include <iostream>
#include <cstdio>
#include <cstdlib>

using namespace std;
 
static pthread_key_t pkt;
// 1, callback function to destroy resource associated with key
// 2, the in_param is pthread_getspecific()
// 3, gettid()是内核给线程(轻量级进程)分配的进程id,全局(所有进程中)唯一
// 4, pthread_self()是在用户态实现的,获取的id实际上是主线程分配给子线程的线程描述符的地址而已,只是在当前进程空间中是唯一的。
void destroy( void *arg )
{
    printf("exit at thread %d, fclose file \n", static_cast<int>( pthread_self() ) );
    if( arg ) fclose( reinterpret_cast<FILE*>(arg) );
}
// 5, pthread_getspecific() Return current value of the thread-specific data slot identified by KEY.
void writeLog( const char* log )
{
    FILE* logHandle = reinterpret_cast<FILE*>( pthread_getspecific( pkt) );
    fprintf( logHandle, "%s\n", log );
}
// 6, pthread_setspecific Store POINTER in the thread-specific data slot identified by KEY 
void* work( void* arg)
{
    FILE* logHandle = NULL;
    char fileName[128] = "";
    sprintf( fileName, "Thread%d.log", static_cast<int>(pthread_self()) );
    logHandle = fopen( fileName, "w");
    pthread_setspecific( pkt, reinterpret_cast<void*>( logHandle ) );
    writeLog( "Thread starting." );
}
// 7, pthread_key_create( &pkt, destroy ) Create a key value identifying a location in the thread-specific      //identifying 识别
//    data area. Each thread maintains a distinct thread-specific data area.
//    the destroy callback function will called with the key is dectroyed
// 8, pthread_key_delete( ) detroy the key use callback function clear the resource
int main(int argc, char const *argv[])
{
    pthread_key_create( &pkt, destroy );
    pthread_t pids[2] = {0};
    pthread_create( &pids[0], NULL, work, NULL );
    pthread_create( &pids[1], NULL, work, NULL );
    pthread_join( pids[0], NULL );
    pthread_join( pids[1], NULL );
    pthread_key_delete( pkt );
    printf("stop\n");
    return 0;
}

ThreadLocal

参考:关键字:__thread & pthread_key_t

对 pthread_key_t 进行了 RAII 的封装,使用更加安全。

#include <pthread.h>
#include <boost/noncopyable.hpp>    // noncopyable
#include <boost/checked_delete.hpp> // check_delete
#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;
}

附录

C++ 获取类中成员函数的函数指针

参见:深入探索C++对象模型之指向成员函数的指针

class A 
{
public:
    static void staticmember(){cout<<"static"<<endl;}   //static member
    void nonstatic(){cout<<"nonstatic"<<endl;}          //nonstatic member
    virtual void virtualmember(){cout<<"virtual"<<endl;};//virtual member
};
int main()
{
    A a;
    //static成员函数,取得的是该函数在内存中的实际地址,而且因为static成员是全局的,所以不能用A::限定符
    void (*ptrstatic)() = &A::staticmember;      
    //nonstatic成员函数 取得的是该函数在内存中的实际地址     
    void (A::*ptrnonstatic)() = &A::nonstatic;
    //虚函数取得的是虚函数表中的偏移值,这样可以保证能过指针调用时同样的多态效果
    void (A::*ptrvirtual)() = &A::virtualmember;
    //函数指针的使用方式
    ptrstatic();
    (a.*ptrnonstatic)();
    (a.*ptrvirtual)();
}

static_cast, dynamic_cast, reinterpret_cast, const_cast

参见:c++ 数据类型转换: static_cast dynamic_cast reinterpret_cast const_cast

  • 上行转换(把子类的指针或引用转换成基类表示), 下行转换(把基类指针或引用转换成子类表示)
  • 类指针或引用的上行转换static_cast 和 dynamic_cast 都可以
  • 类指针或引用的下行转换用dynamic_cast并且判断转换后是否为空
  • 基本数据类型之间的转换用static_cast, 但是由于数值范围的不同,需要用户保证转换的安全性
  • 不同类型之间的指针或引用的转换用reinterpret_cast,它的本质是对指向内存的比特位的重解释
  • 消除数据的const、volatile、__unaligned属性,用const_cast
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容