Notes: folly::ThreadLocalPtr
材料:
- https://github.com/facebook/folly/blob/main/folly/docs/ThreadLocal.md
- 无锁编程实践:RocksDB ThreadLocalPtr剖析 - 杨凿让的文章 - 知乎 https://zhuanlan.zhihu.com/p/398409455
- https://github.com/halty/writing/blob/master/Folly_Source_Insight_Series-ThreadLocalPtr.md
Thread Local 是很常见的东西,errno
就是著名的 thread local 变量。它们通常用作一些优化。关于 thread local,一般认为有下面接口:
- Gcc 等相关的
__thread
: https://gcc.gnu.org/onlinedocs/gcc/Thread-Local.html pthread_key
相关的 POSIX 接口,和其他平台提供的接口.thread_local
等语言关键字,它的初始化类似static
关于 TLS 是怎么做的,下面这篇博客给了一个很好的介绍:https://chao-tic.github.io/blog/2018/12/25/tls
如果对编译、链接、ELF相关的东西感兴趣,还可以看看:https://maskray.me/blog/2021-02-14-all-about-thread-local-storage
folly::ThreadLocal
实现了和 pthread_key
相关 api 一样性能的 TLS,并且提供了 accessAllThreads
的能力(实际上介绍 perfbook 的时候,你会发现大部分东西都需要类似的语义)
Folly 的 ThreadLocalPtr
接口如:ThreadLocalPtr<T, Tag, AccessMethod>
,每个 Tag 会有一组每个线程都有的 StaticMeta<Tag>
对象,StaticMeta<Tag>
对象持有一个 ThreadEntry
的双向链表(ThreadEntryList
),为了维护这个双向链表,它持有了双向链表的 Header (head_
) ,同时,双向链表是很难并发的,所以它持有了一个 mutex,来保证这个链表访问和插入的安全性。
每个线程,如果需要,会有一个 ThreadEntry
对象。他们被 StaticMeta<Tag>
管理,里面有一个 vector
(没有实现成 vector, 但其实可以理解成 vector,没啥问题)。每个 TLS 对象有一个固定的 id, 表示自己在 vector 里面是第几位, 一个 Tag 在每个线程中可能声明了多个 TLS 对象,所以用一个 Vector。TLS 读的时候,会找到本线程的 ThreadEntry
对象,看看自己在 vector
第几个,然后获取 vector 中的对象。对象由 ElementWrapper
包装,这个 Wrapper 包装了一下用户的析构函数。
StaticMeta::getThreadEntrySlow
用来创建线程的 TLS 对象(ThreadEntry
),把 ThreadEntry
对象注册在 pthread_getspecific
中。这个和 OS 有什么区别呢?答案在于原本是多个 Key,现在每个 tag 只需要一个 key。
使用 TLS 的时候,会拿到 StaticMeta<Tag>
的单例对象,然后在调用获取本线程对象的时候,调用 StaticMeta::get(EntryID*)
,来获取本线程的 TLS 对象。每个 ThreadLocalPtr
持有一个 id_
,这个 id_
实现是一个 EntryID
, 保存了一个 atomic<uint32_t>
。这个 id 也是由 StaticMeta<Tag>
分配的。需要注意的是,一些紧张情况下,ThreadEntry
的 ElementWrapper
数组会需要扩容,这个时候要占用全局的锁,然后一个个扩容,会很费。
accessAll
的时候,也要占据 StaticMeta<Tag>
的锁,此外,还有个 accessALlThreadsLock_
,不过我没看出来这个玩意有什么特别大的意义。他们会根据 TLS 对象的 id 遍历每个数组,然后访问对应 index 上的对象。
上图来自博客:https://github.com/halty/writing/blob/master/Folly_Source_Insight_Series-ThreadLocalPtr.md
感谢允许灌水
在使用上,folly 的 HazardPointer
、CachedIntCounter
等容器都使用了 TLS ptr,而 RocksDB 自己写了一套 ThreadLocalPtr
,但是实现的机制差不多,少了 Tag 之类的,多维护了一个更新的语义,详见:https://zhuanlan.zhihu.com/p/398409455