新书《Go语言并发之道》中的一个例子,现实生活中,我们应该都遇到过一种情况,两人在走廊相遇,遇见时为了避让走了另外的方向,结果对方也更改了方向,导致尴尬的情况出现,然后继续换方向,继续尴尬。
这是一种活锁的现象,两个或两个以上的并发进程试图在没有外力协调的情况下防止死锁,他们都在遇到竞态条件时选择了放弃当前所拥有的资源,但是没有沟通好,导致进一步活锁出现,这就好比,如果走廊里的人都同意,只有一个人会移动的话,那么就不会存在活锁,一个人会站着不动,另一个人会移动到另一边,他们的工作就得以继续。
看一下代码
package main
import (
"bytes"
"fmt"
"sync"
"sync/atomic"
"time"
)
func main() {
cadence := sync.NewCond(&sync.Mutex{})
go func() {
for range time.Tick(3*time.Millisecond) {
// 每隔 1ms 发送一次广播
cadence.Broadcast() // 唤醒所有等待中的 goroutine
}
}()
takeStep := func(name string) {
cadence.L.Lock()
fmt.Printf("%v is trying to get a lock from cadence!\n", name)
// 下方的 Wait 方法会在内部自动解锁和加锁
// 解锁在进入 wait 状态后进行,所有实际上可以有多个 goroutine 实现这样一个加锁的流程
// 加锁会在得到信号后再加锁
// source code
/**
func (c *Cond) Wait() {
c.checker.check()
t := runtime_notifyListAdd(&c.notify)
c.L.Unlock()
runtime_notifyListWait(&c.notify, t)
c.L.Lock()
}
*/
cadence.Wait()
cadence.L.Unlock()
}
tryDir := func(dirName, name string, dir *int32, out *bytes.Buffer) bool {
// 写入当前尝试方向
fmt.Fprintf(out, " %v", dirName)
// 两个人会同时将 dir 加上 1
atomic.AddInt32(dir, 1)
takeStep(name) // 两人必须一起做出动作,等待广播信号
if atomic.LoadInt32(dir) == 1 {
// 如果该方向上数字为 1, 那么说明两个人走向了不同的方向,陈国穿过走廊
fmt.Fprintf(out, " . Success!")
return true
}
takeStep(name) // 发现不对劲,两人都不走当前方向
atomic.AddInt32(dir, -1)
return false
}
var left, right int32
tryLeft := func(out *bytes.Buffer, name string) bool { return tryDir("left", name, &left, out)}
tryRight := func(out *bytes.Buffer, name string) bool { return tryDir("right", name, &right, out)}
walk := func(walking *sync.WaitGroup, name string) {
var out bytes.Buffer
defer func() { fmt.Println(out.String()) }()
defer walking.Done()
fmt.Fprintf(&out, "%v is trying to scoot:", name)
for i := 0; i < 5; i++ { // 不会一直等待,5 次即可
// 保证方向一致,先尝试走左边,然后再走右边
if tryLeft(&out, name) || tryRight(&out, name) {
return
}
}
// 愤怒地举起双手
fmt.Fprintf(&out, "\n%v tosses her hands up in exasperation", name)
}
var peopleInHallway sync.WaitGroup
peopleInHallway.Add(2)
go walk(&peopleInHallway, "Alice")
go walk(&peopleInHallway, "Barbara")
peopleInHallway.Wait()
}