brpc doubly buffered data
doubly buffered data提供两个接口,Read
和Modify
This data structure makes Read() almost lock-free by making Modify() much slower. It's very suitable for implementing LoadBalancers which
have a lot of concurrent read-only ops from many threads and occasional modifications of data. As a side effect, this data structure can store a thread-local data for user.
doubly buffered data的核心思路已经写到了他的名字上。
用两个buffer来存储数据。当写者来修改的时候,他会修改后台的buffer,并把它换到前台。而读者则会读到前台buffer中的数据。
为了保证写者退出后,所有的读者都可以写者修改的数据。doubly buffered data用了类似RCU的思路。每个读者在读取数据之前会上一个thread local的锁。而写者在修改数据,切换buffer之后,则会获取每个reader的锁。从而保证后续的reader可以读到新的数据。
然后来看看代码
Wrapper
先看Wrapper。Wrapper就是用来代表读者实例的对象。上面说的thread local的锁就是放到这个Wrapper里
创建的时候初始化mutex,析构的时候释放掉mutex。同时从他属于的DoublyBufferedData
中移除掉。表示移除这个reader。
BeginRead
和EndRead
就是读者的临界区,对应上锁放锁。
WaitReadDone
则是写者调用的。用来等待读者结束读。
在添加Wrapper和移除Wrapper的时候要上一个Wrapper的mutex。用来保护_wrappers
回来看一下DoublyBufferedData
这个结构
核心就是Read
和Modify
对于读的话,会首先在TLS Group中创建一个,或者获取一个Wrapper。然后添加到当前的DoublyBufferedData
中
然后通过BeginRead获取锁。读到fore ground的数据,在ptr中添加数据,以及对应的wrapper。在ScopedPtr析构的时候,就会调用Wrapper的EndRead,从而放锁。
对于Modify来说。首先通过_modify_mutex
定序。获取到bg index。就是后台数据的下标。通过fn来处理数据。这时候bg data就是最新的版本。然后更新_index
,语义是release,这样后续的读者通过acquire获取到index的时候,就可以保证看到index中正确的数据。
然后获取wrapper mutex,防止进来新的读者,以及其他的读者退出。并通过WaitReadDone保证所有读者都结束了这次读。最后修改前台的数据为最新的版本,并返回。
到这里其实核心的地方已经结束了。接下来我们看看Wrapper的管理。
在代码中对应的就是WrapperTLSGroup
在DoublyBufferedData
在注册的时候会在WrapperTLSGroup
中申请一个key。后续的Wrapper的申请就都靠这个key来定位。
通过得到的这个Id得到对应的ThreadBlock,然后得到对应的Wrapper。每个DoublyBufferedData
只会获取一个TLSId。对应在thread local的这个块状vector中的某一个位置。
所以Wrapper的申请和释放其实和TLS Id的获取绑定了。
文章评论