thread_local的工作原理剖析

1 作用:用来存储线程局部变量。此变量相当于线程是独占的,不与其它线程共享。

2 为什么需要线程局部变量?

为了解决全局变量或者静态变量在多线程操作中存在的问题,一个典型的例子就是errno. errno通常用于存储程序当中上一次发生的错误。早期它是一个静态变量,由于当时大多数程序是单线程,因此没有任何问题。但是到了多线程时代,这种errno就不能满足需求了。设想一下,线程A在某个时刻发生了错误,正想获取其错误码,这个时间线程B执行某个函数之后修改了这个错误码,那么线程A接下来获取到的错误码自然不是它真正想要的那个。这个时候就需要将errno设计成thread_local变量了。

3 如何验证thread_local变量是生效的?

也就是说如何验证某个变量是单独属于某个线程的,以下面代码为例:

#include <thread>
#include <vector>

std::vector<int> myVector = {1, 2, 3, 4, 5};

void func(uint32_t times)
{
    while(--times > 0)
    {
        if(times % 2 != 0)
        {
            myVector.push_back(0);
        }
        else
        {
            myVector.pop_back();
        }
    }

    printf("Finished.\r\n");
}

int main()
{
    uint32_t times = 10000000;
    std::thread t1(func, times);
    std::thread t2(func, times);

    t1.join();
    t2.join();

    return 0;
}

如上所示,此代码运行之后必定crash, 原因是t1和t2共享myVector变量, 但是myVector是非线程安全的,并且访问myVector并没有做互斥保护。运行的效果可能如下:

现在将代码修改一下,将myVector变量声明为thread_local变量:

#include <thread>
#include <vector>

thread_local std::vector<int> myVector = {1, 2, 3, 4, 5};//声明为thread_local变量

void func(uint32_t times)
{
    while(--times > 0)
    {
        if(times % 2 != 0)
        {
            myVector.push_back(0);
        }
        else
        {
            myVector.pop_back();
        }
    }

    printf("Finished.\r\n");
}

int main()
{
    uint32_t times = 10000000;
    std::thread t1(func, times);
    std::thread t2(func, times);

    t1.join();
    t2.join();

    return 0;
}

编译并运行:

可以看到程序正常运行,不会crash. 说明线程t1和t2操作的myVector是不同的对象,是各自一份的,所以不存在线程安全问题。

4 那么thread_local变量的实现原理是什么?

考虑以下代码:

#include <thread>

thread_local int var = 100;

int func1()
{
    return var;
}

int main()
{
    std::thread t(func1);
    t.join();
    return 0;
}

将上面代码作反汇编,查看func1的汇编代码如下 :

0000000000400cf8 <_Z5func1v>:
  400cf8:       55                      push   %rbp
  400cf9:       48 89 e5                mov    %rsp,%rbp
  400cfc:       64 8b 04 25 fc ff ff    mov    %fs:0xfffffffffffffffc,%eax
  400d03:       ff
  400d04:       5d                      pop    %rbp
  400d05:       c3                      retq

重点关注这条汇编:

 400cfc:       64 8b 04 25 fc ff ff    mov    %fs:0xfffffffffffffffc,%eax

这个汇编的意思如果用类似C语言的方式表达出来:eax = *(fs+0xfffffffffffffffc),其中0xfffffffffffffffc是-4的补码,即eax = *(fs-4), 即将fs寄存器往后编移4个字节的内容赋值给eax, 众所周知,eax一般用于存储返回值,那么eax中存储的即是100. 也就是代表*(fs-4)是100。 那么fs寄存器又是什么呢?在x86中,fs寄存器用于记录线程。当发生线程切换时,操作系统会更新fs的值,使其指向当前线程的TLS区域(Thread-Local Storage)。所以,可以看到,如果声明了thread_local变量,那么该变量实际存储在线程的TSL区域。那么TSL区域又实际上位于哪里呢?实际上,位于线程的上下文中。操作系统在创建不同时,会创建出对应的上下文。而不同的线程创建出来的上下文往往是不同的,所以对应的寄存器值fs值往往也是不同的。这样就巧妙地区分了不同线程的“私有”thread_local变量了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一条叫做nemo的鱼

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值