|
|
- 多執行緒使用總結
- 一 執行緒局部存儲 (TLS)
- 來自:[url]http://msdn2.microsoft.com/en-us/library/ms686749.aspx[/url]
-
- 同一進程中的所有執行緒共享相同的虛擬地址空間。不同的執行緒中的局部變量有不同的副本,但是static和globl變量是同一進程中的所有執行緒共享的。使用TLS技術可以為static和globl的變量,根據當前進程的執行緒數量創建一個array,每個執行緒可以通過array的index來訪問對應的變量,這樣也就保證了static和global的變量為每一個執行緒都創建不同的副本。
- 二 執行緒局部存儲(TLS)實現使用
- 1)TLS 的 API 實現
- 通過 Win32 API 層和編譯器實現「執行緒本地存儲」。有關詳細信息,請參見 Win32 API 文檔中的 TlsAlloc、TlsGetValue、TlsSetValue 和 TlsFree。 (下面的代碼對msdn的稍加修改)
- #include <windows.h>
- #include <stdio.h>
- #define THREADCOUNT 10
- DWORD dwTlsIndex;
- static
- int g_x =
- 100; // test static var for multiple threading
- VOID ErrorExit(LPSTR);
- VOID CommonFunc(VOID)
- {
- LPVOID lpvData;
- // Retrieve a data pointer for the current thread.
- lpvData = TlsGetValue(dwTlsIndex);
- if ((lpvData ==
- 0) && (GetLastError() != ERROR_SUCCESS))
- ErrorExit("TlsGetValue error");
- int
- *pg_x = (int*)lpvData;
- // Use the data stored for the current thread.
- printf("thread %d: g_x adress=%lx,g_x copy ++ = %d\n"、GetCurrentThreadId()、pg_x、*pg_x);
- Sleep(1000);
- }
- DWORD WINAPI ThreadFunc(VOID)
- {
- LPVOID lpvData;
- // Initialize the TLS index for this thread.
- lpvData = (LPVOID) LocalAlloc(LPTR、256);
- //*(int*)lpvData = g_x;
- int
- *pg_x = (int*)lpvData;
- *pg_x = g_x;
- if (! TlsSetValue(dwTlsIndex、lpvData))
- ErrorExit("TlsSetValue error");
- printf("thread %d: g_x adress=%lx,g_x copy = %d\n"、GetCurrentThreadId()、pg_x、*pg_x);
- InterlockedExchangeAdd(reinterpret_cast<long*>(pg_x),1);
- CommonFunc();
- // Release the dynamic memory before the thread returns.
- lpvData = TlsGetValue(dwTlsIndex);
- if (lpvData !=
- 0)
- LocalFree((HLOCAL) lpvData);
- return
- 0;
- }
- int main(VOID)
- {
- DWORD IDThread;
- HANDLE hThread[THREADCOUNT];
- int i;
- printf("main thread: g_x is :%d\n",g_x);
- // Allocate a TLS index.
- if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)
- ErrorExit("TlsAlloc failed");
- //test for multiple static or global var
- int dwTlsIndex2 = TlsAlloc();
- // Create multiple threads.
- for (i =
- 0; i < THREADCOUNT; i++)
- {
- hThread = CreateThread(NULL、// default security attributes
- 0, // use default stack size
- (LPTHREAD_START_ROUTINE) ThreadFunc、// thread function
- NULL, // no thread function argument
- 0, // use default creation flags
- &IDThread); // returns thread identifier
- // Check the return value for success.
- if (hThread == NULL)
- ErrorExit("CreateThread error\n");
- }
- for (i =
- 0; i < THREADCOUNT; i++)
- WaitForSingleObject(hThread、INFINITE);
- TlsFree(dwTlsIndex);
- printf("main thread: g_x is :%d\n",g_x);
- return
- 0;
- }
- VOID ErrorExit (LPSTR lpszMessage)
- {
- fprintf(stderr、"%s\n"、lpszMessage);
- ExitProcess(0);
- }
- 運行結果:(可以看出不同的執行緒中的g_x變量有不同的地址,即有不同的拷貝,主執行緒變量在開始和最後都不被改變。)
- PS: (TLS的實現原理與API解釋)
- 1:在多執行緒的進程中,為每一個static或是global變量創建一個void*的數組,使變量不同執行緒都有一個拷貝,然後拷貝的指針放入Void*的數組中。
- 2:TlsAlloc() 得到對應於一個static或global變量所對應的void*的數組的索引,這個用來標示不同static或global變量所對應的void*的數組。
- 3:LocalAlloc()用來分配變量在此執行緒的拷貝的指針。
- 4:TlsSetValue()用來把剛分配的指針加到所對應的void*的數組中,加入後一般對會對此指針賦值供此執行緒使用。
- 5:TlsGetValue()用來從對應的void*的數組中找到此執行緒所對應的拷貝的指針。
- 6: TlsFree() 釋放整個void*的指針數組。
- 2)TLS 的編譯器實現
- 為了支援 TLS,已將新屬性 thread 添加到了 C 和 C++ 語言,並由 Visual C++ 編譯器支援。使用 __declspec 關鍵字聲明 thread 變量。例如,以下代碼聲明了一個整數執行緒局部變量,並用一個值對其進行初始化:
- __declspec( thread ) int tls_i = 1;
- 下面的代碼使用了VC提供的__declspec關鍵字來實現TLS,如果不使用TLS,全局變量g_x則線上程中輸出的結果都是1,2,3,4.。。。但是如果使用TLS則線上程中輸出的結果都是1,編譯器幫我們保證了每個執行緒都有一個副本。 我們還可以看到主執行緒在開始和最後輸出的g_x都是0,這也更說明了線上程有不同的副本。
- #include <windows.h>
- #include <stdio.h>
-
- #define THREADCOUNT 10
- DWORD dwTlsIndex;
- //static int g_x = 0;
- #define Thread __declspec(thread)
- Thread static
- int g_x =
- 0;
- VOID ErrorExit(LPSTR);
-
- DWORD WINAPI ThreadFunc(VOID)
- {
- InterlockedExchangeAdd(reinterpret_cast<long*>(&g_x),1); //g_x+=1;
- printf("thread id: %d、g_x++ = %d、g_x adress = %d\n",GetCurrentThreadId(),g_x,&g_x);
- return
- 1;
- }
-
- int main(VOID)
- {
- DWORD IDThread;
- HANDLE hThread[THREADCOUNT];
- int i;
- printf("main thread: g_x = %d、g_x adress = %d\n",g_x,&g_x);
-
- // Create multiple threads.
- for (i =
- 0; i < THREADCOUNT; i++)
- {
- hThread = CreateThread(NULL、// default security attributes
- 0, // use default stack size
- (LPTHREAD_START_ROUTINE) ThreadFunc、// thread function
- NULL, // no thread function argument
- 0, // use default creation flags
- &IDThread); // returns thread identifier
-
- // Check the return value for success.
- if (hThread == NULL)
- ErrorExit("CreateThread error\n");
- }
-
- for (i =
- 0; i < THREADCOUNT; i++)
- WaitForSingleObject(hThread、INFINITE);
-
- printf("main thread: g_x = %d、g_x adress = %d\n",g_x,&g_x);
- return
- 0;
- }
-
- VOID ErrorExit (LPSTR lpszMessage)
- {
- fprintf(stderr、"%s\n"、lpszMessage);
- ExitProcess(0);
- 運行結果:
- 三 使用VC關鍵字實現TLS需要注意:
- 聲明靜態綁定執行緒的本地對像和變量時必須遵守下列原則:
- thread 屬性只能應用於資料聲明和定義。它不能用於函數聲明或定義。例如,以下代碼將生成一個編譯器錯誤: #define Thread __declspec( thread )
- Thread void func(); // This will generate an error.
- 只能在具有 static 作用域的資料項上指定 thread 修飾符。包括全局資料對像(包括 static 和 extern)、本地靜態對像和 C++ 類的靜態資料成員。不可以用 thread 屬性聲明自動資料對象。以下代碼將生成編譯器錯誤: #define Thread __declspec( thread )
- void func1()
- {
- Thread int tls_i; // This will generate an error.
- }
- int func2( Thread int tls_i ) // This will generate an error.
- {
- return tls_i;
- }
- 執行緒本地對象的聲明和定義必須全都指定 thread 屬性。例如,以下代碼將生成錯誤: #define Thread __declspec( thread )
- extern int tls_i; // This will generate an error、since the
- int Thread tls_i; // declaration and definition differ.
- thread 屬性不能用作類型修飾符。例如,以下代碼將生成一個編譯器錯誤: char __declspec( thread ) *ch; // Error
- C++ 類不能使用 thread 屬性。但是,可以使用 thread 屬性將 C++ 類對像實例化。例如,以下代碼將生成一個編譯器錯誤: #define Thread __declspec( thread )
- class Thread C // Error: classes cannot be declared Thread.
- {
- // Code
- };
- C CObject; 因為允許使用 thread 屬性的 C++ 對象的聲明,因此下面兩個示例在語義上是等效的:
- #define Thread __declspec( thread )
- Thread class B
- {
- // Code
- } BObject; // OK--BObject is declared thread local.
- class B
- {
- // Code
- };
- Thread B BObject; // OK--BObject is declared thread local.
- 不將執行緒本地對象的地址視為常數,並且涉及此類地址的任何表達式都不視為常數。在標準 C 中,這種作法的效果是禁止將執行緒本地變量的地址用作對像或指針的初始值設定項。例如,C 編譯器將以下代碼標記為錯誤: #define Thread __declspec( thread )
- Thread int tls_i;
- int *p = &tls_i; //This will generate an error in C. 但是,此限制不適用於 C++。因為 C++允許動態初始化所有對象,因此可以用使用執行緒本地變量地址的表達式初始化對象。實現此操作的方式與實現執行緒本地對像結構的方式相同。例如,以上顯示的代碼在作為 C++ 源檔案編譯時不會生成錯誤。請注意:只有在其中獲取地址的執行緒仍然存在的情況下,執行緒本地變量的地址才有效。
- 標準 C 允許使用涉及引用自身的表達式初始化對像或變量,但只適用於非靜態作用域的對象。雖然 C++ 通常允許使用涉及引用自身的表達式動態初始化對象,但是這種類型的初始化不允許用於執行緒本地對象。例如: #define Thread __declspec( thread )
- Thread int tls_i = tls_i; // Error in C and C++
- int j = j; // OK in C++、error in C
- Thread int tls_i = sizeof( tls_i ) // Legal in C and C++ 請注意:包含正在初始化的對象的 sizeof 表達式不建立對自身的引用且在 C 和 C++ 中都是合法的。
- C++ 不允許此類對執行緒資料的動態初始化,因為將來可能要對執行緒本地存儲功能進行增強。
- 如果 DLL 將任何非本地資料或對像聲明為 __declspec(執行緒),動態加載該 DLL 時會導致保護錯誤。使用 LoadLibrary 加載所有 DLL 後,每當代碼引用非本地 __declspec(執行緒)資料時,將導致系統故障。由於執行緒的全局變量空間是在運行時分配的,因此此空間的大小是以應用程序的需求和所有靜態鏈接的 DLL 的需求相加為基礎計算出來的。使用 LoadLibrary 時,無法擴展此空間以允許放置用 __declspec(執行緒)聲明的執行緒本地變量。如果 DLL 可能是用 LoadLibrary 加載的,請在 DLL 中使用 TLS API(如 TlsAlloc)來分配 TLS。
- 四 DLL使用TLS :[url]http://msdn2.microsoft.com/en-us/library/ms686997.aspx[/url]
複製代碼 |
|