[英]What does the thread_local mean in C++11?
我對C ++ 11中thread_local
的描述感到困惑。 我的理解是,每個線程在函數中都有局部變量的唯一副本。 全局/靜態變量可以被所有線程訪問(可能使用鎖進行同步訪問)。 而且thread_local
變量對所有線程可見,但是只能由為其定義的線程修改嗎? 這是正確的嗎?
線程本地存儲持續時間是一個術語,用於指代看似全局或靜態存儲持續時間的數據(從使用該函數的功能的角度來看),但實際上每個線程只有一個副本。
它增加了當前的自動(存在於塊/功能中),靜態(存在於程序期間)和動態(存在於分配與釋放之間的堆中)。
在線程創建時會引入線程局部的東西,並在線程停止時將其丟棄。
以下是一些示例。
考慮一個隨機數生成器,必須在每個線程的基礎上維護種子。 使用線程局部種子意味着每個線程都獨立於其他線程而獲得自己的隨機數序列。
如果您的種子是隨機函數中的局部變量,則每次調用它時都會對其進行初始化,從而每次都給您相同的數字。 如果是全局變量,線程將相互干擾。
另一個示例是strtok
之類的東西,其中令牌化狀態是在特定於線程的基礎上存儲的。 這樣,一個線程可以確保其他線程不會加重其標記化工作,同時仍然能夠通過多次調用strtok
來維持狀態-這基本上使strtok_r
(線程安全版本)具有冗余性。
這兩個示例都允許線程局部變量存在於使用它的函數中。 在預線程代碼中,它只是函數中的靜態存儲持續時間變量。 對於線程,將其修改為線程本地存儲持續時間。
另一個例子是類似errno
東西。 您不希望在一個調用失敗之后但在可以檢查變量之前,單獨的線程修改errno
,但每個線程只想要一個副本。
該站點對不同的存儲期限說明符有合理的描述。
聲明變量thread_local
每個線程都有自己的副本。 當您通過名稱引用它時,將使用與當前線程關聯的副本。 例如
thread_local int i=0;
void f(int newval){
i=newval;
}
void g(){
std::cout<<i;
}
void threadfunc(int id){
f(id);
++i;
g();
}
int main(){
i=9;
std::thread t1(threadfunc,1);
std::thread t2(threadfunc,2);
std::thread t3(threadfunc,3);
t1.join();
t2.join();
t3.join();
std::cout<<i<<std::endl;
}
此代碼將輸出“ 2349”,“ 3249”,“ 4239”,“ 4329”,“ 2439”或“ 3429”,但不會輸出其他任何內容。 每個線程都有自己的i
副本,該副本分配給i
,遞增並打印。 運行線程的main
也有其自己的副本,該副本在開始時分配給它,然后保持不變。 這些副本是完全獨立的,並且每個都有不同的地址。
在這方面,只有名字是特殊的---如果使用thread_local
變量的地址,則只有指向普通對象的普通指針,可以在線程之間自由傳遞。 例如
thread_local int i=0;
void thread_func(int*p){
*p=42;
}
int main(){
i=9;
std::thread t(thread_func,&i);
t.join();
std::cout<<i<<std::endl;
}
由於i
的地址已傳遞給線程函數,因此即使屬於thread_local
,也可以將屬於主線程的i
的副本分配給該線程。 因此,該程序將輸出“ 42”。 如果這樣做,則需要注意*p
所屬的線程退出后,才可以訪問*p
,否則您將獲得一個懸空的指針和未定義的行為,就像銷毀指向對象的任何其他情況一樣。
thread_local
變量是在“首次使用之前”初始化的,因此,如果給定線程從未接觸過它們,那么就不必對其進行初始化。 這是為了使編譯器可以避免為完全獨立的線程構建程序中的每個thread_local
變量,並且不涉及任何線程。 例如
struct my_class{
my_class(){
std::cout<<"hello";
}
~my_class(){
std::cout<<"goodbye";
}
};
void f(){
thread_local my_class unused;
}
void do_nothing(){}
int main(){
std::thread t1(do_nothing);
t1.join();
}
在此程序中,有2個線程:主線程和手動創建的線程。 兩個線程都不調用f
,因此從不使用thread_local
對象。 因此,未指定編譯器將構造0,1還是2個my_class
實例,並且輸出可能是“”,“ hellohellogoodbyegoodbye”或“ hellogoodbye”。
線程本地存儲在各個方面都像靜態(=全局)存儲一樣,只是每個線程都具有對象的單獨副本。 對象的生存期從線程啟動(對於全局變量)或首次初始化(對於塊局部靜態變量)開始,並在線程結束時(即,調用join()
時)結束。
因此,只能將也可以聲明為static
變量聲明為thread_local
,即全局變量(更確切地說:“在命名空間范圍內”的變量),靜態類成員和塊靜態變量(在這種情況下隱含static
)。
例如,假設您有一個線程池,並且想知道您的工作負載平衡得如何:
thread_local Counter c;
void do_work()
{
c.increment();
// ...
}
int main()
{
std::thread t(do_work); // your thread-pool would go here
t.join();
}
這將打印線程使用情況統計信息,例如使用以下實現:
struct Counter
{
unsigned int c = 0;
void increment() { ++c; }
~Counter()
{
std::cout << "Thread #" << std::this_thread::id() << " was called "
<< c << " times" << std::endl;
}
};
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.