Concurrency In Go 001

作者:guoxj
浏览:369

    新书《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()
}

 




登录后回复

共有0条评论