【双buffer无锁设计】
对于词典热加载等典型的“一写多读”场景,可以同过双buffer实现无锁切换新旧版本。
支持数据快照
• 为避免一条请求处理时前后数据不一致,会对全局数据做0拷贝快照, 只有在请求执行完成时才会更新数据
使用两个指针,一个指针指向当前使用的数据版本,所有读线程通过该指针读取数据,另一个是备份指针,写线程通过该指针写入,当写操作完成后对两个指针进行切换,完成数据更新。
直接切换会导致读线程获取到的数据失效,甚至可能访问的无效内存导致出core,因此通过智能指针封装这两个指针,当读指针的引用计数为1没有读线程使用时切换两个指针。
切换时会出现读写线程竞态因此需要加自旋锁。原因是切换操作的频率很低,而且临界区很小不会自旋太长时间。
对于同一请求会话里中途会不会出现指针切换导致前后数据不一致,可以将数据指针(智能指针)存放在Context对象里,从Context中取用数据。
双buffer大致实现:
template <class T>
class DualBuffer {
public:
std::shared_ptr<T> GetPtr() const {
LockGuard lock(lock_);
return ptr_;
}
void ReloadBuffer() {
...//新数据写入 ptr_back_
std::shared_ptr<T> old_ptr = GetPtr();
{
LockGuard lock(lock_);
ptr_ = ptr_back_; //新数据可以被使用了
}
while (old_ptr.use_count() > 1) {
::usleep(10000);
}
// 没有读线程使用旧数据了
ptr_back_ = old_ptr;
ptr_back_->Clear();
}
private:
std::shared_ptr<T> ptr_;
std::shared_ptr<T> ptr_back_;
SpinLock lock_;
};