mgo中的atomic.Value

闲话

最近在刷MIT的分布式课程,线程和锁一定是最基本的元素啦.
由于GO本身提倡的 Share memory by communicating; don't communicate by sharing memory.,所以在实现的时候试图不用sharing memory + lock而多用channel来实现,这时候就带来了一些小小的不方便,比如一个字符串s可以被多个goroutine读而被一个goroutine写,显然我们要将其加锁.
因为不想加锁于是我试图用atomic来解决这个问题,其中有一个Value类型我发现很有趣,于是决定写下来.

源代码分析

  • atomic.Value分为两个操作,通过Store()存储Value,通过Load()来读取Value的值.

  • 源码我就不贴了,贴一个关键的struct:

    type ifaceWords struct {
    	typ  unsafe.Pointer
    	data unsafe.Pointer
    }
    

ifaceWords结构体是实际存储我们的Value值的地方,可以看到,我们存储的实际是指向Value的type和data的指针.

  • Store操作有两种行为模式:

    • First Store : 当我们第一次调用Store的时候,Store函数会初始化typ指针(需要注意的是,每一个Value在第一次Store之后typ就被确定而不能更改了,否则会panic).
      如果typ==nil则函数会调用runtime_procPin(没找到实现,但注释中说是active spin wait)
      随后调用原子操作函数CompareAndSwapPointer(typ, nil, unsafe.Pointer(^uintptr(0))),如果此时结果返回false说明typ已经不等于nil(被其他goroutine修改过),于是调用runtime_procUnpin解锁并重新进行Store过程.
      如果原子操作函数返回了true,即typ == nil,那么存储typ以及data的指针.

    • 后面的每次Store调用都是直接替换掉data指针

  • Load函数检测typ的值,如果为nil或者正在进行首次调用Store则会返回nil.否则返回一个interface{}(实际存储的是ifaceWords值)

用例

  • 在MIT的课程中,我们设计的每一个Server都需要访问viewService来获得最新的Primary/Backup数据库服务器视图.
    这时候其实对这个View的操作就是周期写,不定时读.这时候就是一个使用Value的合适场景(也可以使用原子函数CompareAndSwapPointer).

  • Go官网上给出的例子

    type Map map[string]string
    var m Value
    m.Store(make(Map))
    var mu sync.Mutex // used only by writers
    // read function can be used to read the data without further synchronization
    read := func(key string) (val string) {
            m1 := m.Load().(Map)
            return m1[key]
    }
    // insert function can be used to update the data without further synchronization
    insert := func(key, val string) {
            mu.Lock() // synchronize with other potential writers
            defer mu.Unlock()
            m1 := m.Load().(Map) // load current value of the data structure
            m2 := make(Map)      // create a new value
            for k, v := range m1 {
                    m2[k] = v // copy all data from the current object to the new one
            }
            m2[key] = val // do the update that we need
            m.Store(m2)   // atomically replace the current object with the new one
            // At this point all new readers start working with the new version.
            // The old version will be garbage collected once the existing readers
            // (if any) are done with it.
    }
    
posted @ 2017-04-11 23:08  XLLL  阅读(2119)  评论(0编辑  收藏  举报