• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            統(tǒng)計

            • 隨筆 - 50
            • 文章 - 42
            • 評論 - 147
            • 引用 - 0

            留言簿(6)

            隨筆分類

            文章分類

            Link

            搜索

            •  

            積分與排名

            • 積分 - 165530
            • 排名 - 159

            最新評論

            閱讀排行榜

            評論排行榜

            使用線程局部存儲TLS

            Thread local storage (TLS)統(tǒng)一進程的多個線程可以通過由TlsAlloc方法返回的索引值在線程自身的空間內(nèi)存儲和取回一個值。在以下這個例子里,索引值在進程開始時創(chuàng)建,當各個線程啟動時,會各自申請一塊動態(tài)內(nèi)存并且將內(nèi)存指針通過TlsSetValue方法存儲到各自的TLS空間中(由先前的索引值標定)。CommonFunc方法使用TlsGetValue方法通過索引取得數(shù)據(jù)指針。在各個線程結束前,釋放動態(tài)內(nèi)存塊。在進程結束見,調(diào)用TlsFree方法釋放索引。

             1#include <windows.h> 
             2#include <stdio.h> 
             3 
             4#define THREADCOUNT 4 
             5DWORD dwTlsIndex; 
             6 
             7VOID ErrorExit(LPSTR); 
             8 
             9VOID CommonFunc(VOID) 
            10
            11   LPVOID lpvData; 
            12 
            13// Retrieve a data pointer for the current thread. 
            14 
            15   lpvData = TlsGetValue(dwTlsIndex); 
            16   if ((lpvData == 0&& (GetLastError() != ERROR_SUCCESS)) 
            17      ErrorExit("TlsGetValue error"); 
            18 
            19// Use the data stored for the current thread. 
            20 
            21   printf("common: thread %d: lpvData=%lx\n"
            22      GetCurrentThreadId(), lpvData); 
            23 
            24   Sleep(5000); 
            25}
             
            26 
            27DWORD WINAPI ThreadFunc(VOID) 
            28
            29   LPVOID lpvData; 
            30 
            31// Initialize the TLS index for this thread. 
            32 
            33   lpvData = (LPVOID) LocalAlloc(LPTR, 256); 
            34   if (! TlsSetValue(dwTlsIndex, lpvData)) 
            35      ErrorExit("TlsSetValue error"); 
            36 
            37   printf("thread %d: lpvData=%lx\n", GetCurrentThreadId(), lpvData); 
            38 
            39   CommonFunc(); 
            40 
            41// Release the dynamic memory before the thread returns. 
            42 
            43   lpvData = TlsGetValue(dwTlsIndex); 
            44   if (lpvData != 0
            45      LocalFree((HLOCAL) lpvData); 
            46 
            47   return 0
            48}
             
            49 
            50int main(VOID) 
            51
            52   DWORD IDThread; 
            53   HANDLE hThread[THREADCOUNT]; 
            54   int i; 
            55 
            56// Allocate a TLS index. 
            57 
            58   if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES) 
            59      ErrorExit("TlsAlloc failed"); 
            60 
            61// Create multiple threads. 
            62 
            63   for (i = 0; i < THREADCOUNT; i++
            64   
            65      hThread[i] = CreateThread(NULL, // default security attributes 
            66         0,                           // use default stack size 
            67         (LPTHREAD_START_ROUTINE) ThreadFunc, // thread function 
            68         NULL,                    // no thread function argument 
            69         0,                       // use default creation flags 
            70         &IDThread);              // returns thread identifier 
            71 
            72   // Check the return value for success. 
            73      if (hThread[i] == NULL) 
            74         ErrorExit("CreateThread error\n"); 
            75   }
             
            76 
            77   for (i = 0; i < THREADCOUNT; i++
            78      WaitForSingleObject(hThread[i], INFINITE); 
            79 
            80   TlsFree(dwTlsIndex);
            81
            82   return 0
            83}
             
            84 
            85VOID ErrorExit (LPSTR lpszMessage) 
            86
            87   fprintf(stderr, "%s\n", lpszMessage); 
            88   ExitProcess(0); 
            89}

            90

            常用情景:
            各個線程所處理的對象有所不同,但是所需要的處理卻可能類似,例如多個線程同時處理多個文件,就可以將文件句柄存在在相應的Tls中,在使用相同的接口進行處理
            背景知識:
            每個線程除了共享進程的資源外還擁有各自的私有資源:一個寄存器組(或者說是線程上下文);一個專屬的堆棧;一個專屬的消息隊列;一個專屬的Thread Local Storage(TLS);一個專屬的結構化異常處理串鏈。
            TLS 是一個良好的Win32 特質(zhì),讓多線程程序設計更容易一些。TLS 是一個機制,經(jīng)由它,程序可以擁有全域變量,但在不同的線程里有不同的值。也就是說,進程中的所有線程都可以擁有全域變量,但這些變量其實是特定對某個線程才有意義。例如,你可能有一個多線程程序,每一個線程都對不同的文件寫文件(也因此它們使用不同的文件handle)。這種情況下,把每一個線程所使用的文件handle 儲存在TLS 中,將會十分方便。當線程需要知道所使用的handle,它可以從TLS 獲得。重點在于:線程用來取得文件handle 的那一段碼在任何情況下都是相同的,而從TLS中取出的文件handle 卻各不相同。非常靈巧,不是嗎?有全域變數(shù)的便利,卻又分屬各線程。

             

              雖然TLS 很方便,它并不是毫無限制。在Windows NT 和Windows 95 之中,有64 個DWORD slots 供每一個線程使用。這意思是一個進程最多可以有64 個「對各線程有不同意義」的DWORDs。 雖然TLS 可以存放單一數(shù)值如文件handle,更常的用途是放置指針,指向線程的私有資料。有許多情況,多線程程序需要儲存一堆數(shù)據(jù),而它們又都是與各線程相關。許多程序員對此的作法是把這些變量包裝為C 結構,然后把結構指針儲存在TLS 中。當新的線程誕生,程序就配置一些內(nèi)存給該結構使用,并且把指針儲存在為線程保留下來的TLS 中。一旦線程結束,程序代碼就釋放所有配置來的區(qū)塊。既然每一個線程都有64 個slots 用來儲存線程自己的數(shù)據(jù),那么這些空間到底打哪兒來?在線程的學習中我們可以從結構TDB中看到,每一個thread database 都有64 個DWORDs 給TLS 使用。當你以TLS 函式設定或取出數(shù)據(jù),事實上你真正面對的就是那64 DWORDs。好,現(xiàn)在我們知道了原來那些“對各線程有不同意義的全局變量”是存放在線程各自的TDB中阿。
             
                接下來你也許會問:我怎么存取這64個DWORDS呢?我又怎么知道哪個DWORDS被占用了,哪個沒有被占用呢?首先我們要理解這樣一個事實:系統(tǒng)之所以給我們提供TLS這一功能,就是為了方便的實現(xiàn)“對各線程有不同意義的全局變量”這一功能;既然要達到“全局變量”的效果,那么也就是說每個線程都要用到這個變量,既然這樣那么我們就不需要對每個線程的那64個DWORDS的占用情況分別標記了,因為那64個DWORDS中的某一個一旦占用,是所有線程的那個DWORD都被占用了,于是KERNEL32 使用兩個DWORDs(總共64 個位)來記錄哪一個slot 是可用的、哪一個slot 已經(jīng)被用。這兩個DWORDs 可想象成為一個64 位數(shù)組,如果某個位設立,就表示它對應的TLS slot 已被使用。這64 位TLS slot 數(shù)組存放在process database 中(在進程一節(jié)中的PDB結構中我們列出了那兩個DWORDs)。
             
            下面的四個函數(shù)就是對TLS進行操作的:

              (1)TlsAlloc  

            上面我們說過了KERNEL32 使用兩個DWORDs(總共64 個位)來記錄哪一個slot 是可用的、哪一個slot 已經(jīng)被用。當你需要使用一個TLS slot 的時候,你就可以用這個函數(shù)將相應的TLS slot位置1。 

             (2)TlsSetValue  

            TlsSetValue 可以把數(shù)據(jù)放入先前配置到的TLS slot 中。兩個參數(shù)分別是TLS slot 索引值以及欲寫入的數(shù)據(jù)內(nèi)容。TlsSetValue 就把你指定的數(shù)據(jù)放入64 DWORDs 所組成的數(shù)組(位于目前的thread database)的適當位置中。  

             (3)TlsGetValue  

            這個函數(shù)幾乎是TlsSetValue 的一面鏡子,最大的差異是它取出數(shù)據(jù)而非設定數(shù)據(jù)。和TlsSetValue 一樣,這個函數(shù)也是先檢查TLS 索引值合法與否。如果是,TlsGetValue 就使用這個索引值找到64 DWORDs 數(shù)組(位于thread database 中)的對應數(shù)據(jù)項,并將其內(nèi)容傳回。  

             (4)TlsFree  

            這個函數(shù)將TlsAlloc 和TlsSetValue 的努力全部抹消掉。TlsFree 先檢驗你交給它的索引值是否的確被配置過。如果是,它將對應的64 位TLS slots 位關閉。然后,為了避免那個已經(jīng)不再合法的內(nèi)容被使用,TlsFree 巡訪進程中的每一個線程,把0 放到剛剛被釋放的那個TLS slot 上頭。于是呢,如果有某個TLS 索引后來又被重新配置,所有用到該索引的線程就保證會取回一個0 值,除非它們再調(diào)用TlsSetValue。

            posted on 2008-12-10 21:25 pear_li 閱讀(2117) 評論(4)  編輯 收藏 引用 所屬分類: C++

            評論

            # re: 使用線程局部存儲TLS  2008-12-11 09:47 LOGOS

            終究是線程 “局部” 存儲,看不出和傳參給線程相比,優(yōu)勢在哪里
              回復  更多評論    

            # re: 使用線程局部存儲TLS  2008-12-11 16:36 阿福

            我覺得線程局部存儲的優(yōu)勢不大,除非和編譯器結合起來。
            比如,在線程函數(shù)的棧上定義一個對象,在線程函數(shù)退出的時候,對象自動調(diào)用自己的析構函數(shù)清理空間,同樣能夠達到線程局部存儲的效果。

            所謂線程局部存儲與編譯器結合,是指這樣的效果:
            TLS int errno;
            //假設存在關鍵字TLS,這樣在全局聲明的變量,就會被編譯器自動編譯為線程局部存儲的變量。可惜都沒編譯器支持這樣的操作,相信最新的支持并行的語言會有類似的功能。
              回復  更多評論    

            # re: 使用線程局部存儲TLS  2008-12-12 09:45 guest

            @LOGOS

            當線程不是自己創(chuàng)建那么就很有用了。

            假設A線程會依次執(zhí)行fun1,fun2,fun3函數(shù)(或者回調(diào)吧),fun1要傳遞一些似有數(shù)據(jù)給fun3怎么辦?使用全局變量的時候要處理同步問題,用TLS就不存在這個問題了。最經(jīng)常見到的應用是標準庫里面的strtok等。

            ------------------------------
            C / C + +運行期庫要使用線程本地存儲器(T L S)。由于運行期庫是在多線程應用程序出現(xiàn)前
            的許多年設計的,因此運行期庫中的大多數(shù)函數(shù)是用于單線程應用程序的。函數(shù) s t r t o k就是個
            很好的例子。應用程序初次調(diào)用 s t r t o k時,該函數(shù)傳遞一個字符串的地址,并將字符串的地址
            保存在它自己的靜態(tài)變量中。當你將來調(diào)用s t r t o k函數(shù)并傳遞N U L L時,該函數(shù)就引用保存的字
            符串地址。
            在多線程環(huán)境中,一個線程可以調(diào)用 s t r t o k,然后,在它能夠再次調(diào)用該函數(shù)之前,另一
            個線程也可以調(diào)用 S t r t o k。在這種情況下,第二個線程會在第一個線程不知道的情況下,讓
            s t r t o k用一個新地址來改寫它的靜態(tài)變量。第一個線程將來調(diào)用 s t r t o k時將使用第二個線程的字
            符串,這就會導致各種各樣難以發(fā)現(xiàn)和排除的錯誤。
            為了解決這個問題,C / C + +運行期庫使用了T L S。每個線程均被賦予它自己的字符串指針,
            供s t r t o k函數(shù)使用。需要予以同樣對待的其他C / C + +運行期庫函數(shù)還有a s c t i m e和g m t i m e。
            如果你的應用程序需要嚴重依賴全局變量或靜態(tài)變量,那么T L S能夠幫助解決它遇到的問題。
            但是編程人員往往盡可能減少對這些變量的使用,而更多地依賴自動(基于堆棧的)變量和通過
            函數(shù)的參數(shù)傳遞的數(shù)據(jù)。這樣做是很好的,因為基于堆棧的變量總是與特定的線程相聯(lián)系的。
            標準的C運行期庫一直是由許多不同的編譯器供應商來實現(xiàn)和重新實現(xiàn)的。如果 C編譯器
            不包含標準的C運行期庫,那么就不值得去購買它。編程員多年來一直使用標準的 C運行期庫,
            并且將會繼續(xù)使用它,這意味著s t r t o k之類的函數(shù)的原型和行為特性必須與上面所說的標準C運
            行期庫完全一樣。如果今天重新來設計C運行期庫,那么它就必須支持多線程應用程序的環(huán)境,
            并且必須采取相應的措施來避免使用全局變量和靜態(tài)變量。
            在我的軟件開發(fā)項目中,我總是盡可能避免使用全局變量和靜態(tài)變量。如果你的應用程序
            使用全局變量和靜態(tài)變量,那么建議你務必觀察每個變量,并且了解一下它能否改變成基于堆
            棧的變量。如果打算將線程添加給應用程序,那么這樣做可以節(jié)省大量時間,甚至單線程應用
            程序也能夠從中得到許多好處。
              回復  更多評論    

            # re: 使用線程局部存儲TLS  2008-12-12 18:01 阿福

            樓上的老兄說的對,其思想也就是在編譯器級別支持線程局部存儲的意思吧!
            比如現(xiàn)在C標準庫的errno是這樣的定義
            #define errno geterrno()
            表面上看是一個變量,而實際上去調(diào)用一個函數(shù)來實現(xiàn)。
            在函數(shù)里面實現(xiàn)了將數(shù)據(jù)存儲在自己的棧里面。
              回復  更多評論    
            久久久久久A亚洲欧洲AV冫| 伊人久久精品无码av一区| 91久久精品国产成人久久| 色综合久久最新中文字幕| 99久久伊人精品综合观看| 欧美久久久久久| 粉嫩小泬无遮挡久久久久久| 九九久久精品国产| 亚洲AV成人无码久久精品老人| 亚洲嫩草影院久久精品| 亚洲精品成人久久久| 国产成人久久激情91| 一97日本道伊人久久综合影院| 999久久久免费精品国产| 国产精品久久婷婷六月丁香| 国产成人久久精品区一区二区| 少妇人妻综合久久中文字幕| 四虎国产精品免费久久久| 无码日韩人妻精品久久蜜桃| 久久影院午夜理论片无码 | 奇米影视7777久久精品| 国产精品99久久精品爆乳| 久久99国产精品尤物| 亚洲愉拍99热成人精品热久久| 久久国产精品波多野结衣AV| 国产一级持黄大片99久久| 久久亚洲AV成人出白浆无码国产| 亚洲а∨天堂久久精品| 国产日韩久久久精品影院首页| 久久久久亚洲AV无码网站| 亚洲香蕉网久久综合影视| 久久久久亚洲av成人网人人软件| 亚洲精品视频久久久| 日产久久强奸免费的看| 伊人色综合久久天天网| 亚洲国产精品一区二区三区久久 | 一本伊大人香蕉久久网手机| 久久99精品国产一区二区三区| 国产精品久久免费| 中文精品久久久久国产网址| 一本一道久久精品综合|