muduo_base程式碼剖析之執行緒特定資料的封裝ThreadLocal
阿新 • • 發佈:2018-11-26
執行緒特定資料 (執行緒內私有的全域性變數)來源
- 在單執行緒程式中,我們經常要用到“全域性變數”以實現多個函式間能共享資料
- 在多執行緒環境下,由於資料空間是共享的,因此全域性變數也為所有執行緒共享
- 但有時應用程式設計中有必要提供執行緒私有的全域性變數,僅在某個執行緒中生效,但卻可以跨多個函式訪問
- POSIX執行緒庫通過維護一定的資料結構來解決這個問題,這些資料稱為Thread-specific Data / TSD
- 執行緒特定資料也稱為本地儲存TLS
- 對於POD型別的執行緒本地儲存,可以使用__thread關鍵字
POSIX執行緒特定資料
- 一旦有任意一個執行緒呼叫了pthread_key_create建立了key,那麼所有的執行緒都擁有自己的key
- 可以為特定執行緒,設定特定資料,這樣不同的執行緒就會有不同的特定資料(雖然它們的key相同,但是key對應的實際資料不同)
- 怎麼銷燬執行緒的特定資料呢?答:在pthread_key_create函式中指定key的銷燬的回撥函式
muduo實現的執行緒特定資料ThreadLocal<T>
#ifndef MUDUO_BASE_THREADLOCAL_H
#define MUDUO_BASE_THREADLOCAL_H
#include <muduo/base/Mutex.h> // MCHECK
#include <muduo/base/noncopyable.h>
#include <pthread.h>
namespace muduo
{
template<typename T>
class ThreadLocal : noncopyable
{
private:
pthread_key_t pkey_;
public:
ThreadLocal()
{
//建立執行緒特定資料pkey_,並指定銷燬實際資料的回撥函式destructor
MCHECK(pthread_key_create (&pkey_, &ThreadLocal::destructor));
}
~ThreadLocal()
{
MCHECK(pthread_key_delete(pkey_));
}
T& value()
{
//pthread_getspecific:通過pkey_獲取執行緒特定資料對應的實際資料
T* perThreadValue = static_cast<T*>(pthread_getspecific(pkey_));
if (!perThreadValue) //返回值為空,說明pkey_對應的實際資料還沒建立
{
T* newObj = new T(); //就建立pkey_對應的實際資料
MCHECK(pthread_setspecific(pkey_, newObj)); //為pkey_設定對應的實際資料
perThreadValue = newObj;
}
return *perThreadValue;
}
private:
static void destructor(void *x)
{
T* obj = static_cast<T*>(x);
typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1]; //檢查是否是完全型別
T_must_be_complete_type dummy; (void) dummy;
delete obj;
}
};
測試程式與使用特定資料的方法
使用特定資料的方法
- 將自定義類Test,作為模板引數T傳給ThreadLocal,即muduo::ThreadLocal<Test>
- 定義全域性的特定資料,即muduo::ThreadLocal<Test> testObj;
此時,所有的執行緒都擁有變數testObj,只是每個執行緒中testObj對應的實際資料是不同的 - 在任意一個執行緒中,可以使用testObj.value()函式獲得特定型別對應的實際的Test*,進而使用Test*去訪問Test中的資料
示例程式碼1
#include <muduo/base/ThreadLocal.h>
#include <muduo/base/CurrentThread.h>
#include <muduo/base/Thread.h>
#include <stdio.h>
class Test : muduo::noncopyable
{
public:
Test()
{
printf("tid=%d, constructing %p\n", muduo::CurrentThread::tid(), this);
}
~Test()
{
printf("tid=%d, destructing %p %s\n", muduo::CurrentThread::tid(), this, name_.c_str());
}
const muduo::string& name() const { return name_; }
void setName(const muduo::string& n) { name_ = n; }
private:
muduo::string name_;
};
//定義執行緒特定函式,表示每個執行緒都有自己獨立的特定資料
muduo::ThreadLocal<Test> testObj1;
muduo::ThreadLocal<Test> testObj2;
void print()
{
printf("tid=%d, obj1 %p name=%s\n",
muduo::CurrentThread::tid(),
&testObj1.value(),
testObj1.value().name().c_str());
printf("tid=%d, obj2 %p name=%s\n",
muduo::CurrentThread::tid(),
&testObj2.value(),
testObj2.value().name().c_str());
}
void threadFunc()
{
print();
testObj1.value().setName("changed 1");
testObj2.value().setName("changed 42");
print();
}
int main()
{
//主執行緒
testObj1.value().setName("main one");
print();
//執行緒1
muduo::Thread t1(threadFunc);
t1.start();
t1.join();
//主執行緒
testObj2.value().setName("main two");
print();
pthread_exit(0);
}
示例程式碼2
/*
該示例表達的用意是:
- 所有的執行緒訪問的都是muduo::Singleton<muduo::ThreadLocal<Test> >::instance()物件,是相同的
- 但是每個執行緒中muduo::Singleton<muduo::ThreadLocal<Test> >::instance()物件對應的特定資料value()是執行緒私有的,是不同的
*/
下面程式碼總結為一句話:所有的執行緒公用一個例項instance(),但是各個執行緒擁有自己私有的特定資料instance().value()
#include <muduo/base/Singleton.h>
#include <muduo/base/CurrentThread.h>
#include <muduo/base/ThreadLocal.h>
#include <muduo/base/Thread.h>
#include <stdio.h>
#include <unistd.h>
class Test : muduo::noncopyable
{
public:
Test()
{
printf("tid=%d, constructing %p\n", muduo::CurrentThread::tid(), this);
}
~Test()
{
printf("tid=%d, destructing %p %s\n", muduo::CurrentThread::tid(), this, name_.c_str());
}
const muduo::string& name() const { return name_; }
void setName(const muduo::string& n) { name_ = n; }
private:
muduo::string name_;
};
/*
該示例表達的用意是:
1. 所有的執行緒訪問的都是muduo::Singleton<muduo::ThreadLocal<Test> >::instance()物件
2. 但是每個執行緒中muduo::Singleton<muduo::ThreadLocal<Test> >::instance()物件對應的
特定資料value()是執行緒私有的,是不同的
*/
#define STL muduo::Singleton<muduo::ThreadLocal<Test> >::instance().value()
void print()
{
printf("tid=%d, %p name=%s\n",
muduo::CurrentThread::tid(),
&STL,
STL.name().c_str());
}
void threadFunc(const char* changeTo)
{
print();
STL.setName(changeTo);
sleep(1);
print();
}
int main()
{
STL.setName("main one");
muduo::Thread t1(std::bind(threadFunc, "thread1"));
muduo::Thread t2(std::bind(threadFunc, "thread2"));
t1.start();
t2.start();
t1.join();
print();
t2.join();
pthread_exit(0);
}