線程本地存儲

對於多線程程序,所有線程共享全局和靜態變量,任何線程使用變量之後都會在其他線程可見,因此對於執行順序非常重要的場景,需要使用多重方式來進行同步確保線程安全。但是,如果希望每個線程單獨擁有一個全局或靜態變量,所有線程都可以使用它,但是在每個線程中是單獨存儲的,那麼就需要使用線程本地存儲。

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*));

具體使用步驟:

  1. 創建一個類型爲 pthread_key_t 類型的變量。
  2. 調用 pthread_key_create()來創建該變量。該函數有兩個參數,第一個參數就是上面聲明的 pthread_key_t變量,第二個參數是一個清理函數,用來在線程釋放該線程存儲的時候被調用。該函數指針可以設成 NULL ,這樣系統將調用默認的清理函數。
  3. 當線程中需要存儲特殊值的時候,調用 pthread_setspcific()。該函數有兩個參數,第一個爲前面聲明的 pthread_key_t 變量,第二個爲 void*變量,這樣你可以存儲任何類型的值。
  4. 如果需要取出所存儲的值,調用 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
}
發佈了158 篇原創文章 · 獲贊 42 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章