在Golang中,如果两个goroutine在没有互斥和原子的情况下读取和写入变量,则可能会导致数据争用情况。
使用命令go run --race xxx.go
将检测比赛点。
虽然在src / sync / mutex.go中实现Mutex,但使用以下代码
func (m *Mutex) Lock() {
// Fast path: grab unlocked mutex.
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
return
}
var waitStartTime int64
starving := false
awoke := false
iter := 0
old := m.state // This line confuse me !!!
......
代码old := m.state
让我感到困惑,因为m.state是由不同的goroutine读写的。
以下功能测试明显有竞争条件问题。但是,如果我把它放在mutex.go中,没有种族条件会被发现。
# mutex.go
func Test(){
a := int32(1)
go func(){
atomic.CompareAndSwapInt32(&a, 1, 4)
}()
_ = a
}
如果将它放在src / os / exec.go等其他软件包中,则会发现条件问题。
package main
import(
"sync"
"os"
)
func main(){
sync.Test() // race condition will not detect
os.Test() // race condition will detect
}
首先,golang源总是会改变,所以让我们确保我们看到同样的事情。以1.12发布
https://github.com/golang/go/blob/release-branch.go1.12/src/sync/mutex.go
如你所说锁定功能开始
func (m *Mutex) Lock() {
// fast path where it will set the high order bit and return if not locked
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
return
}
//reads value to decide on the lower order bits
for {
//if statements involving CompareAndSwaps on the lower order bits
}
}
这个CompareAndSwap在做什么?它在int32中以原子方式查看,如果它为0,则将其交换为mutexLocked(1定义为上面的const)并返回true以交换它。然后它立即返回。这是它快速的道路。 goroutine获得了锁定,现在它正在运行,可以开始运行它的受保护路径。
如果它已经是1(mutexLocked),它不会交换它并返回false(它没有交换它)。
然后它读取状态并进入一个循环,它进行原子比较和交换以确定它应该如何表现。
可能的状态是什么?从const块看到的锁定,吵醒和挨饿的组合。
现在,根据goroutine等待等待列表的时间长短,它将优先考虑何时再次检查互斥锁是否空闲。
但是也观察到只有Unlock()可以将mutexLocked位设置回0.在Lock()CAS循环中,设置的唯一位是饥饿和唤醒。你可以有多个读者,但任何时候只有一个写入者并且该编写者是持有互斥锁并且在调用Unlock()之前执行其受保护路径的那个。查看this article了解更多详情。
通过反汇编二进制输出文件,不同包中的Test函数生成不同的代码。
原因是编译器禁止在同步包中生成竞争检测工具。
代码是:
var norace_inst_pkgs = []string{"sync", "sync/atomic"} // https://github.com/golang/go/blob/release-branch.go1.12/src/cmd/compile/internal/gc/racewalk.go
``