Hi,all. I'm reading the source code of sync package recently.
Here is a code snippet in sync/mutex.go.
// src/sync/mutex.go
type Mutex struct {
state int32
sema uint32
}
func (m *Mutex) lockSlow() {
var waitStartTime int64
starving := false
awoke := false
iter := 0
old := m.state // Here!!! not use atomic.LoadInt32()
for {
// Don't spin in starvation mode, ownership is handed off to waiters
// so we won't be able to acquire the mutex anyway.
if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
// Active spinning makes sense.
// Try to set mutexWoken flag to inform Unlock
// to not wake other blocked goroutines.
if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
awoke = true
}
// ...
old = m.state // And here!!! not use atomic.LoadInt32()
continue
}
// ...
}
// ...
}
You can see full context in link1 and link2
I have read The Go Memory Model, It says "Reads and writes of values larger than a single machine word behave as multiple machine-word-sized operations in an unspecified order."
Althought it is usually 32 or 64 bits on modern machine,I think if you want a operator is atomic,you should use atomic.LoadXXX() explicitly.
I think a ordinary read operator like old := m.state in concurrent environment is not safe,it may read a unexpected value.
I wonder whether the oridinary read operation is right? Could you help me? Thanks in advance.
I'm very sorry to disturb you...
Thanks for your reply,eric.I've always focused on this situation,without atomic operator,If goroutine A set a variable v to 0 and goroutine B set the variable a to 1,anther goroutine C would probably get a unexpected value which is neither 0 nor 1 when its read operation of a is interrupted.So I thought the code is at risk when this happens.
But I'm wrong.As Ian Lance Taylor said,Go will ensure the read operation of mutex.state is a unit in this case,so what i said will never happen.And as you said, the CAS operation will failed even if what i said happened.
Thanks for your reply,Ian.I've always had a misunderstanding about atomic operation,I thought atomic operation is only used to protect the CPU instructions from being interrupted.
I have watched some lectures about C++ memory order on youtube.As you mentioned,another function of atomic operation is to synchronize the value between CPU cache and memory.
As eric said, there is no need to read the latest value of mutex.state here.Even if the latest value of mutex.state is read by atomic.LoadInt32(),it may out of date when execute the CAS operation.
I leave the link of lecture here,it may help people who have the same confusion as me.
C++ and Beyond 2012: Herb Sutter - atomic Weapons 1 of 2
Arvid Norberg: The C++ memory model: an intuition