value.go

killtl · · 1399 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

概述

atomic.Value支持不用锁的情况下并发读写"任意类型"数据

前置知识

example

package main

import (
    "sync/atomic"
    "fmt"
)

func main() {
    var v atomic.Value
    v.Store("joker")
    fmt.Println(v.Load())    // joker
    v.Store(1)
    fmt.Println(v.Load())  // panic: sync/atomic: store of inconsistently typed value into Value
}

对于v.Store("joker")触发第一次写入,类型就固定为string了,后面的v.Store(1)尝试写入int类型报错,这也是概述里对任意类型加引号的原因

Struct

type Value struct {
    v interface{}  // 这里用interface{}类型就是为了支持写任意类型
}

// ifaceWords 就是 interface{} 的内部结构 eface
// 这里引入是为了能拆解Value.v的类型和值,方便进行判断和存储
type ifaceWords struct {
    typ  unsafe.Pointer
    data unsafe.Pointer
}

Store

原型

func (v *Value) Store(x interface{})

源码

func (v *Value) Store(x interface{}) {
    if x == nil {
        panic("sync/atomic: store of nil value into Value")
    }
    vp := (*ifaceWords)(unsafe.Pointer(v))  // 拆解v的类型和值
    xp := (*ifaceWords)(unsafe.Pointer(&x))  // 拆解x的类型和值
    for {
        typ := LoadPointer(&vp.typ)  // 原子载入v的类型
        if typ == nil {  // 初次写入
            runtime_procPin()  // 这个不知道干嘛滴
            if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {   // 设置正在初次写入标志
                runtime_procUnpin() // 这个也不知道干嘛滴
                continue
            }
            StorePointer(&vp.data, xp.data) // 写入x的类型
            StorePointer(&vp.typ, xp.typ)  // 写入x的值
            runtime_procUnpin()
            return
        }
        if uintptr(typ) == ^uintptr(0) {
            // 这里检查是否有别的go正在进行初次写入
            continue
        }
        if typ != xp.typ {  // 检查后续的写入和初次写入的数据的类型是否一致
            panic("sync/atomic: store of inconsistently typed value into Value")
        }
        StorePointer(&vp.data, xp.data)  // 写入x的值
        return
    }
}

为什么需要设定一个写入标志位呢,因为每次的写入都是分类型写入和值写入,虽然各自都是原子写入,但是分两步执行在并发的时候就会有ABA的问题,所以需要设定一个标志位,类似锁的效果吧

Load

原型

func (v *Value) Load() (x interface{})

源码

func (v *Value) Load() (x interface{}) {
    vp := (*ifaceWords)(unsafe.Pointer(v))
    typ := LoadPointer(&vp.typ)  // 获得已写入数据的类型
    if typ == nil || uintptr(typ) == ^uintptr(0) {
        // 如果还没有写入或者正在写入
        return nil
    }
    data := LoadPointer(&vp.data) // 获得已写入数据的值
    xp := (*ifaceWords)(unsafe.Pointer(&x)) // 拆解x并分别写入值和类型
    xp.typ = typ
    xp.data = data
    return
}

总结

不得不说,unsafe真的好用,各种越过go的类型系统,不过越好用的东西越要谨慎


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:killtl

查看原文:value.go

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1399 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传