目录

Read Copy Update

目录

RCURead Copy Update这个词最近才听到,是在公司一位老哥的直播分享里听到的。那天在群里没事乱划,突然看到一个直播分享,原本就想进去嫖一眼,结果内容异常硬核,从头看到尾。

topic是用类似JITJust In Time的方式加速Go的json解析,这里顺便给他们打个广告,这个库开源了:https://github.com/bytedance/sonic

分享里提到了RCU这种技术,RCU原本是Linux内核中的一种同步机制,并且其针对的场景是读多写少。分享里他们遇到的场景是需要实现一个buffer,特性是:

  1. 读操作远多于写操作
  2. 希望读操作无锁、无等待

这样的场景非常适合使用RCU这种技术,RCU的基本实现是:

  1. 读使用原子操作读取数据指针,无锁、无等待

  2. 写/更新操作用互斥锁防护

  3. 不直接写/更新,而是先复制一份,对复制的数据进行写/更新

  4. 通过原子操作将数据指针指向刚修改过的副本

    注:由于可能涉及资源释放,所以在有GC的语言上更容易实现

本质上就是牺牲写性能,提升读性能

代码也非常简单,只用互斥锁和一个原子变量就可以实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type RCU struct {
    m sync.Mutex
    p unsafe.Pointer
}

func (r *RCU) Get(key K) V {
    return (*Map[K]V)(atomic.LoadPointer(&r.p))[key]
}

func (r *RCU) Put(key K, val V) {
    r.m.Lock()
    defer r.m.Unlock()
    newm := new(Map[K]V)
    atomic.StorePointer(&r.p, unsafe.Pointer(newm))
}

简单说一下,代码中MapKV都是虚拟的,可以根据需要更换(也不局限于KV)。

主要思想是通过原子操作,来更换指针指向的地址。

对于读,每次都会读出一个安全的数据地址,不会有其他线程修改这段数据,所以无需同步手段。

对于写,同时只能有一个写,否则存在丢失更新,所以加互斥锁防护。

由于写都是在拷贝上进行,所以不会影响读的性能

NICE!!!