Notes: folly::ThreadLocalPtr

材料:

  1. https://github.com/facebook/folly/blob/main/folly/docs/ThreadLocal.md
  2. 无锁编程实践:RocksDB ThreadLocalPtr剖析 - 杨凿让的文章 - 知乎 https://zhuanlan.zhihu.com/p/398409455
  3. https://github.com/halty/writing/blob/master/Folly_Source_Insight_Series-ThreadLocalPtr.md

Thread Local 是很常见的东西,errno 就是著名的 thread local 变量。它们通常用作一些优化。关于 thread local,一般认为有下面接口:

  1. Gcc 等相关的 __thread : https://gcc.gnu.org/onlinedocs/gcc/Thread-Local.html
  2. pthread_key 相关的 POSIX 接口,和其他平台提供的接口.
  3. 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> 分配的。需要注意的是,一些紧张情况下,ThreadEntryElementWrapper 数组会需要扩容,这个时候要占用全局的锁,然后一个个扩容,会很费。

accessAll 的时候,也要占据 StaticMeta<Tag> 的锁,此外,还有个 accessALlThreadsLock_,不过我没看出来这个玩意有什么特别大的意义。他们会根据 TLS 对象的 id 遍历每个数组,然后访问对应 index 上的对象。

ThreadLocalPtr

上图来自博客:https://github.com/halty/writing/blob/master/Folly_Source_Insight_Series-ThreadLocalPtr.md

感谢允许灌水


在使用上,folly 的 HazardPointerCachedIntCounter 等容器都使用了 TLS ptr,而 RocksDB 自己写了一套 ThreadLocalPtr,但是实现的机制差不多,少了 Tag 之类的,多维护了一个更新的语义,详见:https://zhuanlan.zhihu.com/p/398409455