boost::thread_specific_ptr對象的原理及應(yīng)用亏拉,類似Java ThreadLocal

代碼部分不多說,了解Java ThreadLocal的原理的一般都懂坐桩,我們這里重點關(guān)注下在C++ boost中是如何實現(xiàn)ThreadLocal的原理尺棋。
先上代碼,
CMakeLists.txt

cmake_minimum_required(VERSION 2.6)
project(lexical_cast)

add_definitions(-std=c++14)

include_directories("/usr/local/include")
link_directories("/usr/local/lib")
file( GLOB APP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
foreach( sourcefile ${APP_SOURCES} )
    file(RELATIVE_PATH filename ${CMAKE_CURRENT_SOURCE_DIR} ${sourcefile})
    string(REPLACE ".cpp" "" file ${filename})
    add_executable(${file} ${sourcefile})
    target_link_libraries(${file} boost_filesystem boost_thread boost_system boost_serialization pthread boost_chrono)
endforeach( sourcefile ${APP_SOURCES} )

main.cpp

#include <boost/noncopyable.hpp>
#include <boost/thread/tss.hpp>
#include <boost/thread/thread.hpp>

#include <iostream>

#include <cassert>

class connection: boost::noncopyable {
public:
    int open_count_;
    connection(): open_count_(0) {}

    void open();
    void send_result(int result);
};

// 打開連接绵跷,簡單的設(shè)置 open_count_ = 1
void connection::open() {
    assert(!open_count_);
    open_count_ = 1;
}

void connection::send_result(int /*result*/) {}

connection& get_connection();

// 類似Java中的 thread_local變量膘螟,一個線程一份
boost::thread_specific_ptr<connection> connection_ptr;

connection& get_connection() {
    connection* p = connection_ptr.get();
    // 還未初始化
    if(!p) {
        // 初始化連接對象
        std::cerr << "開始初始化連接, 線程ID: " << boost::this_thread::get_id() << std::endl;
        connection_ptr.reset(new connection());
        p = connection_ptr.get();
        p->open();
    }
    return *p;
}

void task() {
    int result = 2;
    get_connection().send_result(result);
}

void run_tasks() {
    for(std::size_t i=0; i<10000000; ++i) {
        task();
    }
}

int main(int argc, char* argv[]) {

    boost::thread t1;
    boost::thread_group threads;
    for(std::size_t i=0; i<4; ++i) {
        threads.create_thread(&run_tasks);
    }
    threads.join_all();
    return 0;
}

程序輸出如下,一共會打印4個線程ID碾局,
說明四個線程會生成四個thread_specific_ptr對象荆残。


圖片.png

我們再來看這個thread_specific_ptr的原理,
照例我們先來畫下類圖净当,


圖片.png

然后先看下thread_specific_ptr類的實現(xiàn)内斯,
boost::thread_specific_ptr類實現(xiàn)


    template <typename T>
    class thread_specific_ptr
    {
    private:
        // 拷貝構(gòu)造和賦值操作符私有化,不允許拷貝構(gòu)造和賦值 
        thread_specific_ptr(thread_specific_ptr&);
        thread_specific_ptr& operator=(thread_specific_ptr&);
        // 定義一個清理函數(shù)類型的函數(shù)指針類型
       //  其類型為 void (*) (T*)
        typedef void(*original_cleanup_func_t)(T*);
      
        // 定義一個范化的清理函數(shù)
        static void default_deleter(T* data)
        {
            delete data;
        }
        // 定義一個清理函數(shù)調(diào)用器
        // 這個調(diào)用器函數(shù)的功能相當(dāng)簡單像啼,就是調(diào)用清理函數(shù)
        static void cleanup_caller(detail::thread::cleanup_func_t cleanup_function,void* data)
        {
            reinterpret_cast<original_cleanup_func_t>(cleanup_function)(static_cast<T*>(data));
        }

         // 清理函數(shù)成員變量
        detail::thread::cleanup_func_t cleanup;

    public:
        typedef T element_type;
         
        // 默認構(gòu)造器俘闯,使用默認的清理函數(shù)
        thread_specific_ptr():
            cleanup(reinterpret_cast<detail::thread::cleanup_func_t>(&default_deleter))
        {}

        // 也可以自定義清理函數(shù)進行構(gòu)造,但是必須符合 void (*)(T*)
        // 類型的描述符 
        explicit thread_specific_ptr(void (*func_)(T*))
          : cleanup(reinterpret_cast<detail::thread::cleanup_func_t>(func_))
        {}
       // 析構(gòu)函數(shù)忽冻,將當(dāng)前線程綁定的 thread_data_base中的
       //  tss_data字段置為空
        ~thread_specific_ptr()
        {
            detail::set_tss_data(this,0,0,0,true);
        }
        // 獲取當(dāng)前線程對應(yīng)的tss_data數(shù)據(jù)真朗,
       // 可以理解為thread_specific_data
       // 將其轉(zhuǎn)換為 T類型指針,返回
        T* get() const
        {
            return static_cast<T*>(detail::get_tss_data(this));
        }
        // 指針操作符重載
        T* operator->() const
        {
            return get();
        }
        // 解引用操作符重載
        typename add_reference<T>::type operator*() const
        {
            return *get();
        }
        T* release()
        {
            T* const temp=get();
            detail::set_tss_data(this,0,0,0,false);
            return temp;
        }
        // reset 
        // 設(shè)置當(dāng)前線程的thread_data_base數(shù)據(jù)中的tss_data字段為新值
        void reset(T* new_value=0)
        {
            T* const current_value=get();
            if(current_value!=new_value)
            {
                detail::set_tss_data(this,&cleanup_caller,cleanup,new_value,true);
            }
        }
    };
}

所以說看到這里最重要的其實就是set_tss_data和get_tss_data兩個全局函數(shù)的實現(xiàn)了僧诚。
但是這兩個全局函數(shù)是線程庫里面的遮婶,編譯到了so庫文件中,直接看是看不到的湖笨。
可以轉(zhuǎn)到 boost源碼的下載目錄旗扑,例如,/home/fredric/software/boost_1_71_0/libs/thread/慈省,然后用vscode打開當(dāng)前目錄臀防,做一個全局搜索。
set_tss_data的實現(xiàn)辫呻,

 void set_tss_data(void const* key,
                          detail::tss_data_node::cleanup_caller_t caller,
                          detail::tss_data_node::cleanup_func_t func,
                          void* tss_data,bool cleanup_existing)
        {
            if(tss_data_node* const current_node=find_tss_data(key))
            {
                if(cleanup_existing && current_node->func && (current_node->value!=0))
                {
                    (*current_node->caller)(current_node->func,current_node->value);
                }
                if(func || (tss_data!=0))
                {
                    current_node->caller=caller;
                    current_node->func=func;
                    current_node->value=tss_data;
                }
                else
                {
                    erase_tss_node(key);
                }
            }
            else if(func || (tss_data!=0))
            {
                add_new_tss_node(key,caller,func,tss_data);
            }
        }
    }

      void add_new_tss_node(void const* key,
                              detail::tss_data_node::cleanup_caller_t caller,
                              detail::tss_data_node::cleanup_func_t func,
                              void* tss_data)
        {
            detail::thread_data_base* const current_thread_data(get_or_make_current_thread_data());
            current_thread_data->tss_data.insert(std::make_pair(key,tss_data_node(caller,func,tss_data)));
        }

  struct BOOST_THREAD_DECL thread_data_base:
            enable_shared_from_this<thread_data_base>
        {
            thread_data_ptr self;
            pthread_t thread_handle;
            boost::mutex data_mutex;
            boost::condition_variable done_condition;
            bool done;
            bool join_started;
            bool joined;
            boost::detail::thread_exit_callback_node* thread_exit_callbacks;
            std::map<void const*,boost::detail::tss_data_node> tss_data;
  ......
     };
     struct tss_data_node
        {
            typedef void(*cleanup_func_t)(void*);
            typedef void(*cleanup_caller_t)(cleanup_func_t, void*);

            cleanup_caller_t caller;
            cleanup_func_t func;
            void* value;

            tss_data_node(cleanup_caller_t caller_,cleanup_func_t func_,void* value_):
                caller(caller_),func(func_),value(value_)
            {}
        };

      
     // get_tss_data方法的實現(xiàn)
      void* get_tss_data(void const* key)
        {
            if(tss_data_node* const current_node=find_tss_data(key))
            {
                return current_node->value;
            }
            return 0;
        }
   
     tss_data_node* find_tss_data(void const* key)
        {
            detail::thread_data_base* const current_thread_data(get_current_thread_data());
            if(current_thread_data)
            {
                std::map<void const*,tss_data_node>::iterator current_node=
                    current_thread_data->tss_data.find(key);
                if(current_node!=current_thread_data->tss_data.end())
                {
                    return &current_node->second;
                }
            }
            return 0;
        }

可以看到第一set_tss_data的時候清钥,會調(diào)用add_new_tss_node方法,把數(shù)據(jù)通過key->value pair的形式插入到 當(dāng)前線程的thread_data_base的tss_data字段中放闺。
調(diào)用get_tss_data方法其實就是根據(jù)當(dāng)前的thread_specific_ptr對象來獲取tss_data的值祟昭。
整個代碼邏輯非常清晰而且簡單。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末怖侦,一起剝皮案震驚了整個濱河市篡悟,隨后出現(xiàn)的幾起案子谜叹,更是在濱河造成了極大的恐慌,老刑警劉巖搬葬,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荷腊,死亡現(xiàn)場離奇詭異,居然都是意外死亡急凰,警方通過查閱死者的電腦和手機女仰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抡锈,“玉大人疾忍,你說我怎么就攤上這事〈踩” “怎么了一罩?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長撇簿。 經(jīng)常有香客問我聂渊,道長,這世上最難降的妖魔是什么四瘫? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任汉嗽,我火速辦了婚禮,結(jié)果婚禮上找蜜,老公的妹妹穿的比我還像新娘诊胞。我一直安慰自己,他們只是感情好锹杈,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著迈着,像睡著了一般竭望。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上裕菠,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天咬清,我揣著相機與錄音,去河邊找鬼奴潘。 笑死旧烧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的画髓。 我是一名探鬼主播掘剪,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼奈虾!你這毒婦竟也來了夺谁?” 一聲冷哼從身側(cè)響起廉赔,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎匾鸥,沒想到半個月后蜡塌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡勿负,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年馏艾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奴愉。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡琅摩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出躁劣,到底是詐尸還是另有隱情迫吐,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布账忘,位于F島的核電站志膀,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鳖擒。R本人自食惡果不足惜溉浙,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蒋荚。 院中可真熱鬧戳稽,春花似錦、人聲如沸期升。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽播赁。三九已至颂郎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間容为,已是汗流浹背乓序。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留坎背,地道東北人替劈。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像得滤,于是被迫代替她去往敵國和親陨献。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容