zoukankan      html  css  js  c++  java
  • 线程局部存储(thread_local)

    C++11中的几种存储

    序号类型存储器及链接方式备注

    1

    auto

    自动存储期

    该关键字用于两种情况:
    1. 声明变量时: 根据初始化表达式自动推断变量类型。
    2. 声明函数作为函数返回值的占位符。

    2

    static

    静态或线程存储期,同时提示是内部链接

    static变量只初始化一次,除此之外它还有可见性的属性:
    1. static修饰函数内的“局部”变量时,表明它不需要在进入或离开函数时创建或销毁。且仅在函数内可见。
    2. static修饰全局变量时,表明该变量仅在当前(声明它的)文件内可见。
    3. static修饰类的成员变量时,则该变量被该类的所有实例共享。

    3

    register

    自动存储期,提示编译器将此变量置于寄存器上

    寄存器变量。该变量存储在CPU寄存器中,而不是RAM(栈或堆)中。
    该变量的最大尺寸等于寄存器的大小。由于是存储于寄存器中,因此不能对该变量进行取地址操作。

    4

    extern

    静态或线程存储期,同时提示是外部链接

    引用一个全局变量。当在一个文件中定义了一个全局变量时,就可以在其它文件中使用extern来声明并引用该变量。

    5

    mutable

    不影响存储期或链接

    仅适用于类成员变量。以mutable修饰的成员变量可以在const成员函数中修改。

    6

    thread_local

    线程存储期

    线程周期

    thread_local的生命周期

    • 线程局部存储(Thread Local Storage,TLS)是一种存储期(storage duration),对象的存储是在线程开始时分配,线程结束时回收,每个线程有该对象自己的实例。

    thread_local的原理与实现

    参考链接:

    https://www.cnblogs.com/zhoug2020/p/6497709.html

    https://www.jianshu.com/p/495ea7ce649b

    哪些变量可以被声明为thread_local

    • 只对声明于命名空间作用域的对象、声明于块作用域的对象及静态数据成员允许。
    • thread_local作为类成员变量时必须是static的。

    1. 命名空间下的全局变量
    2. 类的static成员变量
    3. 本地局部变量
    4. 文件静态变量
    thread_local int x;  //A thread-local variable at namespace scope
    class X
    {
        static thread_local std::string s; //A thread-local static class data member
    };
    static thread_local std::string X::s;  //The definition of X::s is required
    
    void foo()
    {
        thread_local std::vector<int> v;  //A thread-local local variable
    }

    thread_local修饰的变量具有如下特性

    • 变量在线程创建时生成(不同编译器实现略有差异,但在线程内变量第一次使用前必然已构造完毕)。
    • 线程结束时被销毁(析构,利用析构特性,thread_local变量可以感知线程销毁事件)。
    • 每个线程都拥有其自己的变量副本。
    • thread_local可以和static或extern联合使用,这将会影响变量的链接属性。

    测试Demo

    #include <iostream>
    #include <thread>
    
    
    thread_local int gCount = 10;
    
    class Test {
    public:
        Test() {
        };
        void test(const std::string &name) {
            thread_local int count = 1;
            ++count;
            _count += 100;
            gCount += 10;
            std::cout << name << "--count: " << count << std::endl;
            std::cout << name << "--gCount: " << gCount << std::endl;
            std::cout << name << "--_count: " << _count << std::endl;
    
        }
    private:
        static thread_local int _count;
    };
    
    thread_local int Test::_count = 100;
    
    
    void func(const std::string &name) {
        Test a1;
        a1.test(name);
        a1.test(name);
        Test a2;
        a2.test(name);
        a2.test(name);
    }
    
    
    int main() {
        std::thread t1(func, "t1");
        t1.join();
        std::cout << std::endl;
        std::thread t2(func, "t2");
        t2.join();
        return 0;
    }
    

    结果:

    说明

     对于Windows系统来说,全局变量或静态变量会被放到".data"或".bss"段中,但当使用__declspec(thread)定义一个线程私有变量的时候,编译器会把这些变量放到PE文件的".tls"段中。当系统启动一个新的线程时,它会从进程的堆中分配一块足够大小的空间,然后把".tls"段中的内容复制到这块空间中,于是每个线程都有自己独立的一个".tls"副本。所以对于用__declspec(thread)定义的同一个变量,它们在不同线程中的地址都是不一样的。对于一个TLS变量来说,它有可能是一个C++的全局对象,那么每个线程在启动时不仅仅是复制".tls"的内容那么简单,还需要把这些TLS对象初始化,必须逐个地调用它们的全局构造函数,而且当线程退出时,还要逐个地将它们析构,正如普通的全局对象在进程启动和退出时都要构造、析构一样。Windows PE文件的结构中有个叫数据目录的结构。它总共有16个元素,其中有一元素下标为IMAGE_DIRECT_ENTRY_TLS,这个元素中保存的地址和长度就是TLS表(IMAGE_TLS_DIRECTORY结构)的地址和长度。TLS表中保存了所有TLS变量的构造函数和析构函数的地址,Windows系统就是根据TLS表中的内容,在每次线程启动或退出时对TLS变量进行构造和析构。TLS表本身往往位于PE文件的".rdata"段中。

  • 相关阅读:
    (转)Delphi写COM+的心得体会
    delphi透明组件(控件)开发
    Delphi 常用组件常见属性说明
    DELPHI方面输入EDIT
    BYTE 转字符串
    椭圆按纽制作
    数据库实例学生名册管理系统(DAO的使用实验)
    数据库如何快速创建连接字符串
    数据库使用DataReader的简单实例(两种办法)
    数据库ADO.NET的结构
  • 原文地址:https://www.cnblogs.com/gd-luojialin/p/15027719.html
Copyright © 2011-2022 走看看