代碼部分不多說,了解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對象荆残。
我們再來看這個thread_specific_ptr的原理,
照例我們先來畫下類圖净当,
然后先看下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 ¤t_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的值祟昭。
整個代碼邏輯非常清晰而且簡單。