sync.Once源码的一些疑问

249 阅读3分钟

初学GoLang,看到sync.Once源码处犯了迷糊,查询资料发现很多人都有跟我相似的疑惑,解释也是五花八门,大体都是跟内存模型相关,但看了半天还是懵懵懂懂,这里记录一下自己的理解和疑惑。

package sync

import (
	"sync/atomic"
)

type Once struct {
	done uint32
	m    Mutex
}

func (o *Once) Do(f func()) {
	if atomic.LoadUint32(&o.done) == 0 {
		o.doSlow(f)
	}
}

func (o *Once) doSlow(f func()) {
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

Q1,为什么Do函数中第一个判断要用LoadUint32

doSlow中的storeUnit已经是原子操作,难道不能保证其他线程中能够及时取到正确的值吗

至少在amd64架构下是能保证的,查看atomic源码发现该架构下原子操作就是普通的取值

//Go\src\runtime\internal\atomic\atomic_amd64.go

//go:nosplit//go:nosplit//go:noinlinefunc Load(ptr *uint32) uint32 {    return *ptr}

某些其他架构如arm下则不能保证,有些使用汇编专门实现了该函数

Q2,为什么o.done == 0中取值不使用LoadUnit32

难道是因为唯一会修改o的地方已经使用了原子操作StorUnit32可以保证o对象在线程间的一致性了,按说这里跟上面是一样的,为什么一个直接取值一个用原子操作取值呢?其实还是有一点不一样的,主要这个是在临界区里没有竞争的情况,直接取值即可。

Q3,为什么doSlow中要使用StoreUnit32

Go内存模型中这样说

Note that a read r may observe the value written by a write w that happens concurrently 
with r. Even if this occurs, it does not imply that reads happening after r will observe 
writes that happened before w.

意思是说线程1先后修改了a1,a2,其他线程可能会出现观察到a2,而观察不到a1的情况

那么我们来分析Q3,如果使用普通赋值取代StoreUint32会怎样

package sync

import (
	"sync/atomic"
)

type Once struct {
	done uint32
	m    Mutex
}

func (o *Once) Do(f func()) {
	if atomic.LoadUint32(&o.done) == 0 {//111111
		o.doSlow(f)
	}
}

func (o *Once) doSlow(f func()) {
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {//222222
		defer o.done = 1//333333		f()
	}
}

线程1按照原本我们期望的正常逻辑顺序,1.先执行了f();2.再修改了o.done的值;注意,还没有执行unlock

同一时间,如果一个线程处于程序的11111处,可能会出现已经测到o.done的修改但是还没观测到f()的情况,再往下走出现once()函数返回,但f()没有执行(观测不到),会违反once的语义(Do guarantees that when it returns, f has finished.),

如果使用StoreUnit32则不会发生这种情况,那么为什么呢

TEXT runtime∕internal∕atomic·Store(SB), NOSPLIT, $0-12
	MOVQ	ptr+0(FP), BX
	MOVL	val+8(FP), AX
	XCHGL	AX, 0(BX)
	RET

观察StoreUnit32在Amd64平台下的实现可以发现,使用了XCHGL语句,通过查资料得知,XCHGL语句在多线程条件下有默认的lock语义(汇编层级的),而lock语义也会产生类似上面所说的内存屏障的作用,使之前的修改全部能够被其他线程观测到,所以使用StoreUnit32能够规避上面说的问题