您的位置:首页 > 其它

线程本地存储 TLS

2018-02-27 14:22 369 查看
线程本地存储 TLS(静态TLS)
TLS的概念,以下链接讲的比较详细

静态TLS

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 可以用于修饰对象,能调用对象的构造函数和析构函数,如下示例代码:

hljs cpp">#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" target=_blank>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链接。计数,缓存等。

- 还可以修饰那些”值可能会变,带有全局性,但是又不值得用全局锁保护“的变量。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: