1. 程式人生 > >線程局部存儲TLS

線程局部存儲TLS

tls push_back erp handle sta -128 獲得 返回 null

1 .使用線程局部存儲的理由
   當我們希望這個進程的全局變量變為線程私有時,而不是所有線程共享的,也就是每個線程擁有一份副本時,這時候就可以用到線程局部存儲(TLS,Thread Local Storage)這個機制了。

2.動態TLS
(1)調用TlsAlloc函數
   兩種方式:

1>全局調用一次: global_dwTLSindex=TLSAlloc();

如果程序只調用一次TlsAlloc,而不是每個線程都調用一次TlsAlloc()函數的話,多個線程就使用同一個索引值,不同線程雖然看起來用的是同名的TLS數組索引變量,但實際上各個線程得到的可能是  不同DWORD值。其意義在於,每個使用TLS的線程獲得了一個DWORD類型的線程局部靜態變量作為TLS數組的索引變量

2>進程內的每個線程都調用一次

   每個線程得到不一樣的索引值,調試可以看到每一次調用,索引值的大小是自加1而遞增的。

函數流程:

  1>該函數會檢索系統進程中的位標誌並找到一個FREE標誌,然後將該標誌從FREE改為INUSE,並返回該標誌在位數組中的索引,通常將該索引保存在一個全局變量中,因為這個值會在整個進程範圍內(而不是線程範圍內)使用。
  2>如果TlsAlloc無法在列表中找到一個FREE標誌,會返回TLS_OUT_OF_INDEXES。
  3>TlsAlloc函數在函數返回之前,會遍歷進程中的每個線程,並根據新分配的索引,在每個線程的Tls數組中把對應的元素設為0


(2)調用TlsSetValue(dwTlsIndex,pvTlsValue)

將一個值放到線程的數組中
   該函數把pvTlsValue所標誌的一個PVOID值放到線程的數組中,dwTlsIndex指定了在數組中的具體位置(由TlsAlloc得到)
   當一個線程調用TlsSetValue的時候,會修改自己的數組。但它無法修改另一個線程的TLS數組中的值。
(3)調用PVOID TlsGetValue(dwTlsIndex)

從線程的數組中取回一個值
   與TlsSetValue相似,TlsGetValue中會查看屬於調用線程的數組
(4)調用TlsFree(dwTlsIndex)

   釋放己經預訂的TLS元素
   該函數會將進程內的位標誌數組對應的INUSE標誌重設回FREE
   同時該函數還會將所有線程中該元素的內容設為0。
   試圖釋放一個尚未分配的TLS元素將導致錯誤

// DynamicTLS.cpp : 定義控制臺應用程序的入口點。
//

#include "stdafx.h"
#include <windows.h>
#include <process.h>

#include <ctime>
#include <cstdlib>
#include <vector>
#include <iostream>

using namespace std;

clock_t gc_begin = 0;
clock_t gc_End = 0;
clock_t gc_Interval = 0;   //全局

//unsigned long __stdcall threadProc(void *arg);       CreateThread
unsigned int __stdcall ThreadProc(void *arg);

int main(int argc, char *argv[])
{
	/*
	不同線程雖然看起來用的是同名的TLS數組索引變量,但實際上各個線程得到的可能是不同DWORD值。
	其意義在於,每個使用TLS的線程獲得了一個DWORD類型的線程局部靜態變量作為TLS數組的索引變量。
	*/

	//這裏使用 TlsAlloca() 可以,在線程內部調用這個函數也可以,使得每個線程都有一個不同的索引值,
	//僅僅在這裏調用一次,程序的實現也成功了,不同線程使用了同一個索引值也成功了

	//DWORD tlsIndex = TlsAlloc();    //此步之後,當前線程實際上訪問的是這個TLS數組索引變量的線程內的拷貝版本
	//這裏調用 TlsAlloc()產生的第一個索引值也是1
	DWORD tlsIndex  = 0;
	vector<HANDLE> threads;
	for (int i = 0; i < 2; ++i) {
		HANDLE h = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, 
			(void*)tlsIndex, 0, NULL);
		/*
		使用_beginthreadex 和 CreateThread 創建線程產生的第一個索引值TlsAlloc()不同,
		CreateThread是 1,_beginthreadex產生的是3,占用了兩個索引位?
		*/
		//HANDLE h = (HANDLE)CreateThread(NULL, 0, ThreadProc, 
		//(void*)tlsIndex, 0, NULL);
		Sleep(2000);
		threads.push_back(h);
	}

	for (size_t i = 0; i < threads.size(); ++i) {
		WaitForSingleObject(threads[i], INFINITE);
		CloseHandle(threads[i]);
	}
}
//

unsigned int __stdcall ThreadProc(void *arg)
{
	DWORD tlsIndex = TlsAlloc();
	//DWORD tlsIndex = reinterpret_cast<DWORD>(arg);
	gc_begin = clock();              //從“開啟這個程序進程”到“程序中調用clock()函數”時之間的CPU時鐘計時單元(clock tick)數
	TlsSetValue(tlsIndex, PVOID(gc_begin));  // 利用TlsSetValue 設置 值

	printf("Thread ID: %d, Thread Begin Time: %d\n", GetCurrentThreadId(), gc_begin);

	Sleep(2000);

	gc_End = clock();
	gc_Interval = gc_End - reinterpret_cast<clock_t>(TlsGetValue(tlsIndex));
	double sec = 1.0 * gc_Interval / CLOCKS_PER_SEC; // 利用TlsGetValue取得值

	printf("Thread ID: %d, Thread End Time: %d, Survival Time: %f\n", GetCurrentThreadId(), gc_End, sec);

	return 0;
}

  

  這裏補充一下_beginthreadex()函數和CreateThread函數在創建線程時候,所創建的線程,調用TlsAlloc函數產生的索引值的不同,_beginthreadex()函數下的線程第一個索引值是3,而CreateThread函數下的線程第一個索引值是1,應該是_beginthreadex()函數下的線程出於某種原因占用了兩位:

CreateThread函數下的線程第一個索引值:

技術分享

_beginthreadex()函數下的線程第一個索引值:

技術分享

3.靜態TLS
(1)靜態TLS變量的聲明
  __declspec(thread) int number;
(2)靜態TLS的實現原理
  對於Windows系統來說,正常情況下一個全局變量或靜態變量會被放到".data"或".bss"段中,但當我們使用__declspec(thread)定義一個線程私有變量的時候,編譯器會把這些變量放到PE文件的".tls"段中。


  當系統啟動一個新的線程時,它會從進程的堆中分配一塊足夠大小的空間,然後把".tls"段中的內容復制到這塊空間中,於是每個線程都有自己獨立的一個".tls"副本。所以對於用__declspec(thread)定義的同一個變量,它們在不同線程中的地址都是不一樣的。

  線程環境塊(TEB,Thread Environment Block)。這個結構裏面保存的是線程的堆棧地址、線程ID等相關信息,其中有一個域是一個TLS數組,它在TEB中的偏移是0x2C。對於每個線程來說,x86的FS段寄存器所指的段就是該線程的TEB,於是要得到一個線程的TLS數組的地址就可以通過FS:[0x2C]訪問到。

// Static_TLS.cpp : 定義控制臺應用程序的入口點。
//

#include "stdafx.h"
#include <windows.h>
#include <iostream>


// 定義靜態TLS全局變量
__declspec(thread) int __TlsValue = 0;
using namespace std;
DWORD WINAPI ThreadProcedure(LPVOID ParameterData);
int main()
{

    // 設置主線程靜態TLS的value為5
	__TlsValue = 5;
	HANDLE ThreadHandle = CreateThread(NULL, 0, ThreadProcedure, NULL, 0, NULL);
	if (ThreadHandle)
	{
		// 等待直到子線程結束
		WaitForSingleObject(ThreadHandle, INFINITE);
		// 取得主線程靜態TLS的值
		cout << "主線程 __TlsValue=" << __TlsValue << endl;
		}
    return 0;
}
DWORD WINAPI ThreadProcedure(LPVOID ParameterData)
{
	// 設置子線程value為10,並不影響主線程
	__TlsValue = 10;
	// 取得子線程靜態TLS的值
	cout << "子線程 __TlsValue=" << __TlsValue << endl;
	return 0;
}

  

線程局部存儲TLS