• 如果全局变量只读取 那自然是不需要加锁的
  • 如果全局变量多进程读,多进程写,那自然是需要加读写锁的
  • 但是如果全局变量只有一个进程写,其他进程读呢? 如果采用COW的方式,写进程只是通过单次赋值的方式来更新变量,是否就可以不加锁了呢?
WARNING: DATA RACE

其实核心点在于这个赋值是否是原子的。也就是说是否存在 p1 = p2 的写入时,指针被临时替换为p3,并在此时goroutine切出的情况。可以想到的一种情况是64字节的指针需要两次mv才能完成全部变量的赋值。那么就有可能在两次mv中间切出,进而出现p3的情况。

在go中,唯一保证原子性的操作是在 sync.atomic, 所以如果你想确保原子性,可以使用sync.Mutex 或者 sync.atomic 中的原子函数。 但是我不建议 sync.atomic中函数, 因为你不得不在任何使用指针的地方使用他们,这是非常难做到正确使用的。

用mutex 是好的go style - 你可以很方便的定义一个函数返回指针。 比如

import "sync"
var secretPointer *int
var pointerLock sync.Mutex

func CurrentPointer() *int {
    pointerLock.Lock()
    defer pointerLock.Unlock()
    return secretPointer
}

func SetPointer(p *int) {
    pointerLock.Lock()
    secretPointer = p
    pointerLock.Unlock()
}

所以一个ok的go style 应该是使用 sync.Mutex 的。

golang doc也是这么说的。 https://golang.org/ref/mem

type T struct {
	msg string
}

var g *T

func setup() {
	t := new(T)
	t.msg = "hello, world"
	g = t
}

func main() {
	go setup()
	for g == nil {
	}
	print(g.msg)
}

Even if main observes g != nil and exits its loop, there is no guarantee that it will observe the initialized value for g.msg.

In all these examples, the solution is the same: use explicit synchronization.

go tool asm

因为规范没有指定,所以你应该假设它不是原子的。即使它现在是原子的,它也有可能在不违反规范的情况下改变。

总之我们不应该做赋值原子的假设,而应该按照规范,使用sync.Mutex 来做。

REF