欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++之thread_local變量的一些用法

 更新時間:2024年07月09日 11:52:58   作者:流星雨愛編程  
thread_local 是 C++11 中引入的關(guān)鍵字,用于聲明線程局部存儲,下面這篇文章主要給大家介紹了關(guān)于C++之thread_local變量用法的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下

1.C++ 的存儲類型

1.1.存儲周期(Storage duration)

存儲周期表示一個變量的存儲空間持續(xù)的時間,它應該與對象的語義生命周期一致(或至少不小于對象的語義生命周期)。C++ 98從 C 繼承了三種存儲周期,分別是靜態(tài)存儲周期(static storage duration)、自動存儲周期(automatic storage duration)和動態(tài)存儲周期(dynamic storage duration),C++ 11 又增加了一種線程存儲周期(thread storage duration)。

存儲周期只是一個概念,是程序語義范疇內(nèi)的東西,但不是語法的范疇。這個概念在語法上的表示則由下一節(jié)介紹的存儲類型說明符(Storage class specifiers)展示。

1.2.存儲類型說明符(Storage class specifiers)

存儲類型說明符(Storage class specifiers)也被稱為存儲類型,它們是變量聲明語法中類型說明符的一部分,它們和變量名的范圍一起控制變量的兩個獨立屬性,即存儲周期(storage duration)和鏈接屬性( linkage)。C++ 98從 C 語言繼承了 auto、register、static 和 extern 四種類型,同時補充了一種 mutable,C++ 11 針對線程存儲周期又增加了一個線程本地存儲的說明符 thread_local。關(guān)于這幾個存儲類型說明符的作用,請參考下表:

類型說明備注
auto自動存儲周期,也是變量的默認存儲類型,由變量的域范圍決定變量的存儲周期,比如局部變量的存儲周期隨著域的結(jié)束而結(jié)束,而全局變量的存儲周期則與程序的運行時間一致從 C++11 開始,顯示使用 auto 存儲類型會導致編譯錯誤。比如 auto int i; 會導致編譯錯誤
register也是自動存儲類型,不過暗示編譯器會擇機將其放置在寄存器中以提高數(shù)據(jù)存取的效率在 C++ 17 被移除標準,以后應避免使用這個存儲類型
static靜態(tài)或線程存儲周期,采用內(nèi)部鏈接(對于不屬于匿名名字空間(anonymous namespace)的靜態(tài)類成員,采用外部鏈接)static 表示一個對象具有靜態(tài)存儲持續(xù)周期。它的生命周期是程序的整個執(zhí)行過程,其存儲的值在程序啟動之前只初始化一次
extern靜態(tài)或線程存儲周期,采用外部鏈接
mutable嚴格來說,這不是一種存儲類型,因為它既不影響變量的存儲周期,也不影響鏈接屬性,它只是表示一種可以“不動聲色”地修改常量對象成員的機會。
thread_local線程存儲類型

1.3.存儲類型說明符與存儲周期的關(guān)系

C++ 中變量存儲周期與變量類型說明符的關(guān)系如下表所示:

存儲周期變量類型與類型說明符
自動存儲周期顯式使用 register 聲明的變量,或隱式聲明為 static 或 extern 的作用域內(nèi)部變量,沒有明確指定存儲類型說明符的變量
靜態(tài)存儲周期1、非 thread_local 聲明的全局(非局部)變量;2、非動態(tài)生成(使用 new 創(chuàng)建)的非局部變量;3、用 static 聲明的局部變量、全局變量和類成員變量
動態(tài)存儲周期1、使用 new 表達式創(chuàng)建(非 placement_new),并且使用 delete 銷毀的對象;2、使用其他動態(tài)分配函數(shù)和動態(tài)釋放函數(shù)管理的對象存儲位置
線程存儲周期使用 thread_local 聲明的所有變量,包括局部變量、全局變量和成員變量

2.thread_local簡介

thread_local 是 C++11 為線程安全引進的變量聲明符。表示對象的生命周期屬于線程存儲期。

線程局部存儲(Thread Local Storage,TLS)是一種存儲期(storage duration),對象的存儲是在線程開始時分配,線程結(jié)束時回收,每個線程有該對象自己的實例;如果類的成員函數(shù)內(nèi)定義了 thread_local 變量,則對于同一個線程內(nèi)的該類的多個對象都會共享一個變量實例,并且只會在第一次執(zhí)行這個成員函數(shù)時初始化這個變量實例。

thread_local 一般用于需要保證線程安全的函數(shù)中。本質(zhì)上,就是線程域的全局靜態(tài)變量。

3.thread_local 應用

3.1.thread_local 與全局變量

使用 thread_local 聲明的變量會在每個線程中維護一個該變量的實例,線程之間互不影響,這里我們用一個普通的全局變量和一個 thread_local 類型的全局變量做對比,說明一下這種存儲類型的變量有什么性質(zhì)。

std::mutex print_mtx;    //避免打印被沖斷

thread_local int thread_count = 1;
int global_count = 1;

void ThreadFunction(const std::string& name, int cpinc)
{
    for (int i = 0; i < 5; i++)
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::lock_guard<std::mutex> lock(print_mtx);
        std::cout << "thread name: " << name << ", thread_count = " << thread_count 
                                             << ", global_count = " << global_count++ << std::endl;
        thread_count += cpinc;
    }
}

int main()
{
    std::thread t1(ThreadFunction, "t1", 2);
    std::thread t2(ThreadFunction, "t2", 5);
    t1.join();
    t2.join();
}

輸出:

thread name: t2, thread_count = 1, global_count = 1
thread name: t1, thread_count = 1, global_count = 2
thread name: t1, thread_count = 3, global_count = 3
thread name: t2, thread_count = 6, global_count = 4
thread name: t1, thread_count = 5, global_count = 5
thread name: t2, thread_count = 11, global_count = 6
thread name: t1, thread_count = 7, global_count = 7
thread name: t2, thread_count = 16, global_count = 8
thread name: t1, thread_count = 9, global_count = 9
thread name: t2, thread_count = 21, global_count = 10

可以看出來每個線程中的 thread_count 都是從 1 開始打印,這印證了 thread_local 存儲類型的變量會在線程開始時被初始化,每個線程都初始化自己的那份實例。另外,兩個線程的打印數(shù)據(jù)也印證了 thread_count 的值在兩個線程中互相不影響。作為對比的 global_count 是靜態(tài)存儲周期,就沒有這個特性,兩個線程互相產(chǎn)生了影響。

3.2.thread_local 與 static變量

thread_local 也可以用于局部變量的聲明,其作用域的約束與局部靜態(tài)變量類似,但是其存儲與局部靜態(tài)變量不一樣,首先是每個線程都有自己的變量實例,其次是其生命周期與線程一致,而局部靜態(tài)變量的聲明周期是直到程序結(jié)束。下面再用一個例子演示一下:

void DoPrint(const std::string& name, int cpinc)
{
    static int static_count = 1;
    thread_local int local_count = 1;
    std::cout << "thread name: " << name << ", local_count = " << local_count
        << ", static_count = " << static_count++ << std::endl;
    local_count += cpinc;
}

void ThreadFunction(const std::string& name, int cpinc)
{
    for (int i = 0; i < 5; i++)
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::lock_guard<std::mutex> lock(print_mtx);
        DoPrint(name, cpinc);
    }
}

int main()
{
    std::thread t1(ThreadFunction, "t1", 2);
    std::thread t2(ThreadFunction, "t2", 5);
    t1.join();
    t2.join();
}

在上面的例子中,static_count 和 local_count 變量的作用域都僅限于 DoPrint() 函數(shù)內(nèi)部,但是存儲類型不一樣,local_count 在每個線程中的實例獨立初始化,獨立變化,線程之間沒有影響,而局部靜態(tài)變量 static_count 則在兩個線程之間互相影響。從結(jié)果打印的情況也印證了這一點:

thread name: t1, local_count = 1, static_count = 1
thread name: t2, local_count = 1, static_count = 2
thread name: t1, local_count = 3, static_count = 3
thread name: t2, local_count = 6, static_count = 4
thread name: t2, local_count = 11, static_count = 5
thread name: t1, local_count = 5, static_count = 6
thread name: t1, local_count = 7, static_count = 7
thread name: t2, local_count = 16, static_count = 8
thread name: t1, local_count = 9, static_count = 9
thread name: t2, local_count = 21, static_count = 10

3.3.thread_local 與 成員變量

thread_local 可以用于類的成員變量,但是只能用于靜態(tài)成員變量。這很容易理解,C++ 不能在對象只有一份拷貝的情況下弄出多個成員變量的實例,但是靜態(tài)成員就不一樣了,每個類的靜態(tài)成員共享一個實例,改成線程局部存儲比較容易實現(xiàn),也容易理解。

class B {
public:
    B() {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "create B" << std::endl;
    }
    ~B() {}
    thread_local static int b_key;
    //thread_local int b_key;
    int b_value = 24;
    static int b_static;
};

thread_local int B::b_key = 12;
int B::b_static = 36;

void thread_func(const std::string& thread_name) {
    B b;
    for (int i = 0; i < 3; ++i) {
        b.b_key--;
        b.b_value--;
        b.b_static--;   // not thread safe
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "thread[" << thread_name << "]: b_key:" << b.b_key << ", b_value:" << b.b_value << ", b_static:" << b.b_static << std::endl;
        std::cout << "thread[" << thread_name << "]: B::key:" << B::b_key << ", b_value:" << b.b_value << ", b_static: " << B::b_static << std::endl;
    return;
}

輸出:

create B
thread[t2]: b_key:11, b_value:23, b_static:35
thread[t2]: B::key:11, b_value:23, b_static: 35
thread[t2]: b_key:10, b_value:22, b_static:34
thread[t2]: B::key:10, b_value:22, b_static: 34
thread[t2]: b_key:9, b_value:21, b_static:33
thread[t2]: B::key:9, b_value:21, b_static: 33
create B
thread[t1]: b_key:11, b_value:23, b_static:32
thread[t1]: B::key:11, b_value:23, b_static: 32
thread[t1]: b_key:10, b_value:22, b_static:31
thread[t1]: B::key:10, b_value:22, b_static: 31
thread[t1]: b_key:9, b_value:21, b_static:30
thread[t1]: B::key:9, b_value:21, b_static: 30

b_key 是 thread_local,雖然其也是 static 的,但是每個線程中有一個,每次線程中的所有調(diào)用共享這個變量。b_static 是真正的 static,全局只有一個,所有線程共享這個變量。

3.4.thread_local 與初始化

#include <iostream>
#include <thread>
#include <mutex>
std::mutex cout_mutex;

//定義類
class A {
public:
    A() {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "create A" << std::endl;
    }

    ~A() {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "destroy A" << std::endl;
    }

    int counter = 0;
    int get_value() {
        return counter++;
    }
};

A* creatA() {
    return new A();
}

void loopin_func(const std::string& thread_name) {
    thread_local A* a = creatA();
    std::lock_guard<std::mutex> lock(cout_mutex);
    std::cout << "thread[" << thread_name << "]: a.counter:" << a->get_value() << std::endl;
    return;
}

void thread_func(const std::string& thread_name) {
    for (int i = 0; i < 3; ++i) {    
        loopin_func(thread_name);
    }
    return;
}

int main() {
    std::thread t1(thread_func, "t1");
    std::thread t2(thread_func, "t2");
    t1.join();
    t2.join();
    return 0;
}

輸出:

create A
thread[t1]: a.counter:0
thread[t1]: a.counter:1
thread[t1]: a.counter:2
create A
thread[t2]: a.counter:0
thread[t2]: a.counter:1
thread[t2]: a.counter:2

雖然 createA() 看上去被調(diào)用了多次,實際上只被調(diào)用了一次,因為 thread_local 變量只會在每個線程最開始被調(diào)用的時候進行初始化,并且只會被初始化一次。

舉一反三,如果不是初始化,而是賦值,則情況就不同了:

void loopin_func(const std::string& thread_name) {
    thread_local A* a;
    a = creatA();
    std::lock_guard<std::mutex> lock(cout_mutex);
    std::cout << "thread[" << thread_name << "]: a.counter:" << a->get_value() << std::endl;
    return;
}

輸出:

create A
thread[t1]: a.counter:0
thread[t1]: a.counter:0
thread[t1]: a.counter:0
create A
thread[t2]: a.counter:0
thread[t2]: a.counter:0
thread[t2]: a.counter:0

很明顯,雖然只初始化一次,但卻可以被多次賦值,因此 C++ 變量初始化是十分重要的。

4.thread_local 的用處

在 thread_local 提出之前,你無法為一個線程定義自己的全局變量(線程級別的全局變量),只能將全局變量定義在父進程中,由所有的線程(不同種類的線程)共享使用。但是當程序復雜到一定程度的時候,線程之間的串擾就在所難免,同時也增大了多線程編碼的復雜度。前面的例子展示了 thread_local 的用法,每個線程共享一個屬于本線程的變量的實例,相當于線程有了自己的全局變量。

另一個常用來解釋 thread_local 的意義的例子就是隨機數(shù)的生成。我們知道的隨機數(shù)生成器都是偽隨機數(shù)生成器,其隨機性取決于種子(seed)的變化。如果一個函數(shù)使用局部變量設置隨機數(shù)發(fā)生器的種子,那么它在每個使用這個函數(shù)的線程中都會被初始化,由于使用了相同的種子,每個線程將得到一樣的隨機數(shù)序列,這就使得多線程也不那么隨機了。如果使用 thread_local 類型的種子,則每個線程維護自己的種子,從而使得每個線程都能得到不同的隨機數(shù)序列,真正起到隨機數(shù)的作用。

其他的例子就是線程不安全問題,C 標準庫的錯誤碼 errno,還有 strtok() 等函數(shù)就是線程不安全的例子。有了 thread_local ,就可以用很小的改動解決這些函數(shù)的線程不安全問題。也不需要像有些編譯器那樣,專門提供一套線程安全的標準庫,用過的人都知道,很多函數(shù)的參數(shù)定義都是不兼容的,對現(xiàn)有代碼的改造成本非常高。

5.性能考慮

  • 雖然 thread_local 變量提供了線程間的數(shù)據(jù)隔離,但它們也可能帶來一些性能開銷。
  • 訪問 thread_local 變量通常比訪問常規(guī)的全局變量或棧變量要慢,因為需要進行額外的 TLS 查找操作。
  • 因此,在性能敏感的代碼中應謹慎使用 thread_local

6.替代方案

  • 如果不需要真正的線程局部存儲,但只是想在線程之間傳遞數(shù)據(jù),可以考慮使用線程特定的數(shù)據(jù)(Thread-Specific Data, TSD)機制,如 POSIX 的 pthread_key_create 和 pthread_setspecific 函數(shù)。
  • 對于跨平臺的應用程序,可以使用第三方庫(如 Boost.Thread)來提供類似的功能。

7.總結(jié)

thread_local 關(guān)鍵字為 C++ 程序員提供了一種方便的方式來處理多線程環(huán)境中的線程特定數(shù)據(jù)。通過避免數(shù)據(jù)競爭和簡化同步機制,它可以幫助提高多線程程序的性能和可維護性。然而,在使用時需要注意其性能開銷和跨平臺兼容性問題,并根據(jù)具體場景選擇合適的替代方案。

到此這篇關(guān)于C++之thread_local變量的文章就介紹到這了,更多相關(guān)C++ thread_local變量內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

相關(guān)文章

  • c++中的string常用函數(shù)用法總結(jié)

    c++中的string常用函數(shù)用法總結(jié)

    以下是對c++中的string常用函數(shù)的用法進行了詳細的分析介紹,需要的朋友可以過來參考下
    2013-09-09
  • 詳解C語言用malloc函數(shù)申請二維動態(tài)數(shù)組的實例

    詳解C語言用malloc函數(shù)申請二維動態(tài)數(shù)組的實例

    這篇文章主要介紹了詳解C語言用malloc函數(shù)申請二維動態(tài)數(shù)組的實例的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下
    2017-10-10
  • 深入ORACLE變量的定義與使用的詳解

    深入ORACLE變量的定義與使用的詳解

    本篇文章是對ORACLE變量的定義與使用進行了詳細的分析與介紹,需要的朋友參考下
    2013-05-05
  • C++類模板與函數(shù)模板基礎詳細講解

    C++類模板與函數(shù)模板基礎詳細講解

    C++語言的模板技術(shù)包括函數(shù)模板和類模板,模板技術(shù)是一種代碼重用技術(shù),函數(shù)和類是C++語言中兩種主要的重用代碼形式,這篇文章主要介紹了C++函數(shù)模板和類模板,需要的朋友可以參考下
    2022-08-08
  • C++實現(xiàn)反轉(zhuǎn)鏈表的兩種方法

    C++實現(xiàn)反轉(zhuǎn)鏈表的兩種方法

    本文主要介紹了C++實現(xiàn)反轉(zhuǎn)鏈表的兩種方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-02-02
  • C++實現(xiàn)停車場管理系統(tǒng)的示例代碼

    C++實現(xiàn)停車場管理系統(tǒng)的示例代碼

    停車場管理系統(tǒng)就是模擬停車場進行車輛管理的系統(tǒng),該系統(tǒng)分為汽車信息模塊,用戶使用模塊和管理員用戶模塊,本文將用C++實現(xiàn)這一簡單的系統(tǒng),希望對大家有所幫助
    2023-04-04
  • C++淺析數(shù)據(jù)在內(nèi)存中如何存儲

    C++淺析數(shù)據(jù)在內(nèi)存中如何存儲

    使用編程語言進行編程時,需要用到各種變量來存儲各種信息。變量保留的是它所存儲的值的內(nèi)存位置。這意味著,當您創(chuàng)建一個變量時,就會在內(nèi)存中保留一些空間。您可能需要存儲各種數(shù)據(jù)類型的信息,操作系統(tǒng)會根據(jù)變量的數(shù)據(jù)類型,來分配內(nèi)存和決定在保留內(nèi)存中存儲什么
    2022-08-08
  • C語言qsort()函數(shù)的使用方法詳解

    C語言qsort()函數(shù)的使用方法詳解

    qsort是一個庫函數(shù),基于快速排序算法實現(xiàn)的一個排序的函數(shù),下面這篇文章主要給大家介紹了關(guān)于C語言qsort()函數(shù)使用的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-06-06
  • C++?右值引用與?const?關(guān)鍵字詳解

    C++?右值引用與?const?關(guān)鍵字詳解

    C++中的const關(guān)鍵字的用法非常靈活,而使用const將大大改善程序的健壯性,const關(guān)鍵字是一種修飾符,這篇文章主要介紹了C++?右值引用與?const?關(guān)鍵字,需要的朋友可以參考下
    2022-10-10
  • 詳解Qt中的雙緩沖機制與實例應用

    詳解Qt中的雙緩沖機制與實例應用

    所謂雙緩沖機制,是指在繪制控件時,首先將要繪制的內(nèi)容繪制在一個圖片中,再將圖片一次性地繪制到控件上。本文主要為大家介紹了Qt中的雙緩沖機制與實例應用,希望對大家有所幫助
    2023-03-03

最新評論