设为首页 加入收藏

TOP

RocksDB线程局部缓存(一)
2019-09-23 11:15:29 】 浏览:74
Tags:RocksDB 线程 局部

概述

      在开发过程中,我们经常会遇到并发问题,解决并发问题通常的方法是加锁保护,比如常用的spinlock,mutex或者rwlock,当然也可以采用无锁编程,对实现要求就比较高了。对于任何一个共享变量,只要有读写并发,就需要加锁保护,而读写并发通常就会面临一个基本问题,写阻塞读,或则写优先级比较低,就会出现写饿死的现象。这些加锁的方法可以归类为悲观锁方法,今天介绍一种乐观锁机制来控制并发,每个线程通过线程局部变量缓存共享变量的副本,读不加锁,读的时候如果感知到共享变量发生变化,再利用共享变量的最新值填充本地缓存;对于写操作,则需要加锁,通知所有线程局部变量发生变化。所以,简单来说,就是读不加锁,读写不冲突,只有写写冲突。这个实现逻辑来源于Rocksdb的线程局部缓存实现,下面详细介绍Rocksdb的线程局部缓存ThreadLocalPtr的原理。

线程局部存储(TLS)

简单介绍下线程局部变量,线程局部变量就是每个线程有自己独立的副本,各个线程对其修改相互不影响,虽然变量名相同,但存储空间并没有关系。一般在linux 下,我们可以通过以下三个函数来实现线程局部存储创建,存取功能。

int pthread_key_create(pthread_key_t *key, void (*destr_function) (void*)), 
int pthread_setspecific(pthread_key_t key, const void *pointer) ,
void * pthread_getspecific(pthread_key_t key)

ThreadLocalPtr类

     有时候,我们并不想要各个线程独立的变量,我们仍然需要一个全局变量,线程局部变量只是作为全局变量的缓存,用以缓解并发。在RocksDB中ThreadLocalPtr这个类就是来干这个事情的。ThreadLocalPtr类包含三个内部类,ThreadLocalPtr::StaticMeta,ThreadLocalPtr::ThreadData和ThreadLocalPtr::Entry。其中StaticMeta是一个单例,管理所有的ThreadLocalPtr对象,我们可以简单认为一个ThreadLocalPtr对象,就是一个线程局部存储(ThreadLocalStorage)。但实际上,全局我们只定义了一个线程局部变量,从StaticMeta构造函数可见一斑。那么全局需要多个线程局部缓存怎么办,实际上是在局部存储空间做文章,线程局部变量实际存储的是ThreadData对象的指针,而ThreadData里面包含一个数组,每个ThreadLocalPtr对象有一个独立的id,在其中占有一个独立空间。获取某个变量局部缓存时,传入分配的id即可,每个Entry中ptr指针就是对应变量的指针。

ThreadLocalPtr::StaticMeta::StaticMeta() : next_instance_id_(0), head_(this) {
  if (pthread_key_create(&pthread_key_, &OnThreadExit) != 0) {
    abort();
  }
  ......
}

void* ThreadLocalPtr::StaticMeta::Get(uint32_t id) const {
   auto* tls = GetThreadLocal();
   return tls->entries[id].ptr.load(std::memory_order_acquire);
}

struct Entry {
  Entry() : ptr(nullptr) {}
  Entry(const Entry& e) : ptr(e.ptr.load(std::memory_order_relaxed)) {}
  std::atomic<void*> ptr;
};

整体结构如下:每个线程有一个线程局部变量ThreadData,里面包含了一组ThreadLocalPtr的指针,对应的是多个变量,同时ThreadData之间相互通过指针串联起来,这个非常重要,因为执行写操作时,写线程需要修改所有thread的局部缓存值来通知共享变量发生变化了。

 ---------------------------------------------------
 |          | instance 1 | instance 2 | instnace 3 |
 ---------------------------------------------------
 | thread 1 |    void*   |    void*   |    void*   | <- ThreadData
 ---------------------------------------------------
 | thread 2 |    void*   |    void*   |    void*   | <- ThreadData
 ---------------------------------------------------
 | thread 3 |    void*   |    void*   |    void*   | <- ThreadData

struct ThreadData {
  explicit ThreadData(ThreadLocalPtr::StaticMeta* _inst)
      : entries(), inst(_inst) {}
  std::vector<Entry> entries;
  ThreadData* next;
  ThreadData* prev;
  ThreadLocalPtr::StaticMeta* inst;
};

读写无并发冲突

     现在说到最核心的问题,我们如何实现利用TLS来实现本地局部缓存,做到读不上锁,读写无并发冲突。读、写逻辑和并发控制主要通过ThreadLocalPtr中通过3个关键接口Swap,CompareAndSwap和Scrape实现。对于ThreadLocalPtr< Type* > 变量来说,在具体的线程局部存储中,会保存3中不同类型的值:

  1). 正常的Type* 类型指针;

  2). 一个Type*类型的Dummy变量,记为InUse;

  3). nullptr值,记为obsolote;

读线程通过Swap接口来获取变量内容,写线程则通过Scrape接口,遍历并重置所有ThreadData为(obsolote)nullptr,达到通知其他线程局部缓存失效的目的。下次读线程再读取时,发现获取的指针为nullptr,就需要重新构造局部缓存。

//获取某个id对应的局部缓存内容,每个ThreadLocalPtr对象有单独一个id,通过单例StaticMeta对象管理。
void* ThreadLocalPtr::StaticMeta::Swap(uint32_t id, void* ptr) {
//获取本地局部缓存
auto* tls = GetTh
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Mysql学习之事务的隔离性 下一篇理解大数据

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目