對於多線程程序,所有線程共享全局和靜態變量,任何線程使用變量之後都會在其他線程可見,因此對於執行順序非常重要的場景,需要使用多重方式來進行同步確保線程安全。但是,如果希望每個線程單獨擁有一個全局或靜態變量,所有線程都可以使用它,但是在每個線程中是單獨存儲的,那麼就需要使用線程本地存儲。
pthread庫的實現
經典的pthread線程庫提供了對線程本地存儲的完全支持,具體需要使用如下三個函數:
//將需要共享的變量轉換爲void*指針,綁定到pthread_key_t關聯的全局對象中
int pthread_setspecific(pthread_key_t key, const void *value);
//從綁定到pthread_key_t類型的全局變量中獲取需要的數據,返回類型需要從void*轉換爲實際類型
void *pthread_getspecific(pthread_key_t key);
//創建一個全局關聯變量,第二個參數是指定對用戶關聯的數據進行釋放的handler
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
具體使用步驟:
- 創建一個類型爲
pthread_key_t
類型的變量。 - 調用
pthread_key_create()
來創建該變量。該函數有兩個參數,第一個參數就是上面聲明的pthread_key_t
變量,第二個參數是一個清理函數,用來在線程釋放該線程存儲的時候被調用。該函數指針可以設成 NULL ,這樣系統將調用默認的清理函數。 - 當線程中需要存儲特殊值的時候,調用
pthread_setspcific()
。該函數有兩個參數,第一個爲前面聲明的pthread_key_t
變量,第二個爲void*
變量,這樣你可以存儲任何類型的值。 - 如果需要取出所存儲的值,調用
pthread_getspecific()
。該函數的參數爲前面提到的pthread_key_t
變量,該函數返回void *
類型的值。
C++封裝
針對這些C風格函數操作,下面使用C++模板封裝了一個範型版本。
template<typename T>
class ThreadSpecificUtil {
public:
static void init() {
pthread_once(&_s_once, initKey);
}
static T * get() {
return renterpret_cast<T *>(
pthread_getspecific(_s_key));
}
static int set(T *data) {
int ret = pthread_setspecific(
_s_key,
reinterpret_cast<void *>(data));
return ret;
}
private:
static void initKey() {
pthread_key_create(&_s_key, dataDestructor);
}
static void dataDestructor(void *p) {
if (NULL != p) {
T *data = reinterpret_cast<T *>(p);
delete data;
}
}
private:
static pthread_key_t _s_key;
static pthread_once_t _s_once;
};
template<typename T>
pthread_key_t ThreadSpecificUtil<T>::_s_key;
template<typename T>
pthread_once_t ThreadSpecificUtil<T>::_s_once = PTHREAD_ONCE_INIT;
上述使用的pthread_once
函數用來完成初始化工作,創建一個全局的pthread_key_t
類型的變量,保證多個線程中只有一個來完成這個初始化工作。
C++11
C++11標準新添加了thread_local
關鍵字,用來標識新的存儲類型。
The storage class specifiers are a part of the decl-specifier-seq of a name’s declaration syntax. Together with the scope of the name, they control two independent properties of the name: Its storage duration and its linkage.
auto - automatic storage duration.(until C++11)
register - automatic storage duration. Also hints to the compiler to place the object in the processor’s register. (deprecated)(until C++17)
static - static or thread storage duration and internal linkage
extern - static or thread storage duration and external linkage
thread_local - thread storage duration.(since C++11)
Only one storage class specifier may appear in a declaration except that thread_local may be combined with static or with extern (since C++11)
這個關鍵字用來再變量聲明的時候進行說明的一個語法,與作用域修飾符一起來決定變量的存儲週期和作用域,可以與static和extern複合修飾變量。
經過這個關鍵字定義的變量自動就成爲了與線程關聯的本地變量,每個線程都有一份拷貝,在線程創建時創建變量,線程銷燬時析構變量。示例如下:
thread_local int j = 0;
void foo()
{
m.lock();
j++; // j is now 1, no matter the thread. j is local to this thread.
m.unlock();
}
void func()
{
j = 0;
std::thread t1(foo);
std::thread t2(foo);
t1.join();
t2.join();
// j still 0. The other "j"s were local to the threads
}