线程本地存储 TLS


##线程本地存储 TLS(静态TLS)

TLS的概念,以下链接讲的比较详细

聊聊Linux中的线程本地存储(1)——什么是TLS

静态TLS

上面链接中提到__thread关键字是GCC编译器的扩展,并不属于标准,所以在window下的VC++是没有这个关键字的。不过VC++也提供一个对应的关键字 __declspec来声明线程变量。如下:

__declspec( thread ) int tls_i = 1; 

  • __thread 只能用于修辞POD类型,不能修饰class类型,因为无法自动调用构造函数和析构函数。
  • 在VC++下,__declspec 可以用于修饰对象,能调用对象的构造函数和析构函数,如下示例代码:
#include <thread>
class T
{
public:
    T():v(0)
    {
        std::cout << "create t" << std::endl;
    }

    ~T()
    {
       std::cout << "delete t" << std::endl;
    }

    void PrintValue()
    {
        std::cout << "=========> v:"<< v << std::endl;
    }

    void SetValue(int i)
    {
        v = i;
    }

private:
    int v;
};

__declspec(thread) T t1;

void test()
{
    t1.PrintValue();
}

void test1()
{
    t1.SetValue(16);
}

int main()
{
    std::thread tt1(test1);

    std::thread tt2(test);

    system("pause");
}

线程结束时会分别调用对象的构造及析构函数

windows下TSL 详细见如下链接:
https://msdn.microsoft.com/en-us/library/6yh4a9k1.aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ms686749(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/ms686991(v=vs.85).aspx

C++11提供了thread_local关键字实现了线程存储
https://stackoverflow.com/questions/6021273/how-to-allocate-thread-local-storage
http://en.cppreference.com/w/cpp/language/storage_duration

TLS的应用

在muduo的日志库中作者提到了几处优化的地方,其中有两条就是通过TLS来实现的,我觉得很有借鉴意义。

  • 对日志时间戳的缓存:每一条日志的时间戳格式:20180101 13:00:12 123221。那么在一秒内输出多条日志时,日期和时间是不变的,变的是微秒。那么可以将格式化的时间字符串及精度为秒的时间值缓存起来。只有在跨秒时再重新格式化时间字符串。对于每一条日志,产生该日志的肯定是一个确定线程,对于格式化的时间字符串等变量对该线程来说就是线程私有的。那么对用于缓存的变量是可以用__thread声明的, 一是提高执行效率,而是不用考虑互斥的问题。
__thread char t_time[32];
__thread time_t t_lastSecond;

t_time[32] 是用来缓存为格式后的时间戳字符串,精度是到秒。其被声明为__thread
t_lastSecond 用存储上一秒的时间戳,精度为秒。其被声明为__thread

以下是muduo日志库中时间戳生成的代码

void Logger::Impl::formatTime()
{
  int64_t microSecondsSinceEpoch = time_.microSecondsSinceEpoch();
  time_t seconds = static_cast<time_t>(microSecondsSinceEpoch / 1000000);
  int microseconds = static_cast<int>(microSecondsSinceEpoch % 1000000);
  if (seconds != t_lastSecond)
  {
    t_lastSecond = seconds;
    struct tm tm_time;
    ::gmtime_r(&seconds, &tm_time);

    int len = snprintf(t_time, sizeof(t_time), "%4d%02d%02d %02d:%02d:%02d",
        tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday,
        tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
    assert(len == 17); (void)len;
  }

  //微秒值是单独生成的
  Fmt us(".%06dZ ", microseconds);
  assert(us.length() == 9);
  stream_ << T(t_time, 17) << T(us.data(), 9);
}

对线程ID的缓存,每个线程的ID都是唯一的,私有的。所以非常适合用__thread修辞。在muduo库中有对thread id的缓存及对格式化的缓存。定义在CrrentThread命名空间中,都用 __thread修辞。代码如下:

namespace CurrentThread
{
  // internal
  extern __thread int t_cachedTid;
  extern __thread char t_tidString[32];
  extern __thread const char* t_threadName;
  void cacheTid();

  inline int tid()
  {
    if (t_cachedTid == 0)
    {
      cacheTid();
    }
    return t_cachedTid;
  }

  inline const char* tidString() // for logging
  {
    return t_tidString;
  }

  inline const char* name()
  {
    return t_threadName;
  }

  bool isMainThread();
}

//初始化
namespace CurrentThread
{
  __thread int t_cachedTid = 0;
  __thread char t_tidString[32];
  __thread const char* t_threadName = "unknown";
  const bool sameType = boost::is_same<int, pid_t>::value;
  BOOST_STATIC_ASSERT(sameType);
}

//缓存thread id
void CurrentThread::cacheTid()
{
  if (t_cachedTid == 0)
  {
    t_cachedTid = detail::gettid();
    int n = snprintf(t_tidString, sizeof t_tidString, "%5d ", t_cachedTid);
    assert(n == 6); (void) n;
  }
}

在muduo的日志库中,每条消息都会记录产生该消息的对应的thread id,所以将thread id及格式化的字符串缓存起来也可以提高日志性能。

总结

归纳一下TLS使用的场景:

  • 线程私有的对象,如文件fd,socket链接。计数,缓存等。
  • 还可以修饰那些”值可能会变,带有全局性,但是又不值得用全局锁保护“的变量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

mo4776

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值