線程局部存儲tls的使用

線程局部存儲(Thread Local Storage,TLS)主要用於在多線程中,存儲和維護一些線程相關的數據,存儲的數據會被關聯到當前線程中去,並不須要鎖來維護。。windows

所以也沒有多線程間資源競爭問題,那如何去實現TLS存儲呢,主要有如下幾種方式:api

  1. gcc和clang的__thread修飾符
  2. windows下msvc的__declspec(thread)修飾符
  3. pthread庫pthread_setspecificpthread_getspecific接口
  4. windows下的TlsSetValueTlsGetValue

__thread和__declspec(thread)的使用

其中__thread和__declspec(thread)用起來最爲方便,只須要在static或者全局變量前加上此修飾符,而後在線程裏面訪問變量就好了安全

例如:多線程

tb_void_t tb_thread_func(tb_cpointer_t priv)
{
    // 定義一個線程局部變量
    static __thread int a = 0;
        
    // 初始化這個變量,設置爲當前線程id
    if (!a) a = tb_thread_self();
}

若是運行多個線程的話,上述代碼中每一個線程的變量a的值,都是不相同的,值爲每一個線程的id函數

__declspec(thread) 用起來也是相似,只需替換下 __thread就好了工具

雖然這兩個修飾符用起來很方便,可是須要編譯器支持,雖然如今大部分平臺的編譯器都已支持,可是做爲跨平臺開發,這樣仍是不夠的測試

畢竟仍是有很多低版本gcc,不必定支持__thread,尤爲是嵌入式開發領域,交叉編譯工具鏈中的編譯器支持力度差別仍是蠻大的。。線程

另外,使用__thread進行tls數據維護,須要手動管理相關內存的釋放問題,用的很差很容易致使內存泄露。。code

pthread接口

pthread 的 tls 相關接口,比較完善,而且支持註冊free函數,在線程退出的時候,自動釋放相關tls數據,避免內存泄露,可是使用上稍顯複雜了些協程

咱們看個簡單的例子:

// 測試線程中tls變量存儲的key,需定義爲全局或者static
static pthread_key_t g_local_key = 0;

static tb_void_t tb_thread_local_free(tb_pointer_t priv)  
{  
    tb_trace_i("thread[%lx]: free: %p", tb_thread_self(), priv);
}  
static tb_void_t tb_thread_local_init(tb_void_t)
{
    // 建立tls的key,而且設置自動釋放函數
	pthread_key_create(&g_local_key, tb_thread_local_free);
}
static tb_int_t tb_thread_local_test(tb_cpointer_t priv)
{
    // 在全部線程中,僅執行一次,用於在線程內部初始化 tls 的 key
    static pthread_once_t s_once = PTHREAD_ONCE_INIT;
    pthread_once(&s_once, tb_thread_local_init);

    // 嘗試讀取當前tls數據
    tb_size_t local;
    if (!(local = (tb_size_t)pthread_getspecific(g_local_key)))
    {
        // 設置tls數據爲當前線程id
        tb_size_t self = tb_thread_self();
        if (0 == pthread_setspecific(g_local_key, (tb_pointer_t)self))
            local = self;
    }

    return 0;
}

看上去複雜了些,可是更加靈活,若是不須要在線程內部建立key的話,就不須要調用pthread_once了,直接把建立好的key傳入線程內部去訪問就好。

TlsSetValue 接口

此套接口(TlsSetValue, TlsGetValue, TlsAlloc, TlsFree),屬於windows的tls操做接口,固然不能跨平臺了,使用起來和pthread的差很少,可是沒法註冊自動釋放函數,而且也沒提供相似pthread_once的接口 在線程內部自建立key,功能上稍顯不足。。

static tb_int_t tb_thread_local_test(tb_cpointer_t priv)
{
    // 建立一個tls的key,注:此處非線程安全,最好放到相似pthread_once提供的init函數中去建立
    // 此處就臨時先這麼寫了,僅僅只是爲了方便描述api用法,不要照搬哦。。
    static DWORD s_key = 0;
    if (!s_key) s_key = TlsAlloc();

    // 嘗試讀取當前tls數據
    DWORD local;
    if (!(local = TlsGetValue(s_key))) 
    {
        // 設置tls數據爲當前線程id
        tb_size_t self = tb_thread_self();
        if (TlsSetValue(s_key, (LPVOID)self))
            local = self;
    }

    return 0;
}

其實windows上還提供了FlsAlloc, FlsSetValue系列接口,給協程使用,而且支持註冊自動釋放的回調函數,不過對系統版本有些要求,像xp這些老系統就用不了了。。 這裏就很少描述了。

tbox提供的thread_local接口封裝

最近對tbox的tls接口進行了改造,而且重構了實現邏輯,在剪口易用性、功能性以及效率上都獲得了很大的提高。。

目前支持如下功能:

  • 支持註冊自動釋放回調,保證在線程退出時,自動釋放設置的tls數據
  • 支持在線程內部進行線程安全的key建立
  • tbox退出時會自動銷燬全部建立的key,固然也能夠提早主動銷燬它

用起來也很方便,很pthread很相似,可是內部自動調用了pthread_once,不用想pthread那樣顯式的去調用它了,例如:

static tb_void_t tb_demo_thread_local_free(tb_cpointer_t priv)
{
    tb_trace_i("thread[%lx]: free: %p", tb_thread_self(), priv);
}
static tb_int_t tb_demo_thread_local_test(tb_cpointer_t priv)
{
    /* 線程安全地初始化一個tls對象,至關於key,而且註冊自動free回調
     *
     * 注:雖然全部線程都會執行到這個tb_thread_local_init
     *     可是s_local的tls對象,只會確保初始化一次,內部有相似pthread_once接口來維護
     */
    static tb_thread_local_t s_local = TB_THREAD_LOCAL_INIT;
    if (!tb_thread_local_init(&s_local, tb_demo_thread_local_free)) return -1;

    // 嘗試讀取當前tls數據
    tb_size_t local;
    if (!(local = (tb_size_t)tb_thread_local_get(&s_local)))
    {
        // 設置tls數據爲當前線程id
        tb_size_t self = tb_thread_self();
        if (tb_thread_local_set(&s_local, (tb_cpointer_t)self))
            local = self;
    }

    return 0;
}

在線程退出時,它會自動調用free回調,釋放對應殘留的tls數據,而且在tb_exit退出後,銷燬全部建立的tls對象

固然你能夠能夠主動調用:tb_thread_local_exit(&s_local) 來銷燬它。。

tbox的這套接口,相比pthread減小了一個init的回調函數,比windows那套多了自動釋放的機制,而且同時支持跨平臺。。

雜談

以前我看到一些庫中,對__thread和pthread接口進行了混用,感到非常莫名,我的感受是有問題的,例如:

static __thread pthread_key_t g_key;

本來pthread文檔中就明確表述key須要全局或者static存儲,而這裏加上__thread後,其實每一個線程訪問的key都不是同一個key了哦。。

總結

若是僅僅只是想用來在線程內部存儲一些簡單的int數據,而且不考慮完備的跨平臺支持,那麼建議直接使用__thread或者__declspec(thread)j就好了,很是方便易用

若是要考慮跨平臺操做的話,tbox的tls接口也是個不錯的選擇哦。。

我的主頁:TBOOX開源工程

相關文章
相關標籤/搜索