为什么互斥体代码会停止另一个完整的 go-routine?

var m *sync.RWMutex

func main() {

    m = new(sync.RWMutex)

    n := 100

    go func() {

        for i := 0; i < n; i++ {

            write("WA", i)

        }

    }()


    go func() {

        for i := 0; i < n; i++ {

            write("WB", i)

        }

    }()


    select {}

}

func write(tag string, i int) {

    m.Lock()

    fmt.Printf("[%s][%s%d]write start \n", tag, tag, i)

    time.Sleep(100 * time.Millisecond)

    fmt.Printf("[%s][%s%d]write end \n", tag, tag, i)

    m.Unlock()


    // time.Sleep(1 * time.Millisecond)

}

结果在控制台:


go run mutex.go

[WB][WB0]写开始

[WB][WB0]写结束

[WB][WB1]写开始

[WB][WB1]写结束

[WB][WB2]写开始

[WB][WB2] ]write end

[WB][WB3]write start

[WB][WB3]write end

[WB][WB4]write start

[WB][WB4]write end

[WB][WB5]write start

[WB][WB5]write end

[WB][WB6]写入开始

[WB][WB6]写入结束

[WB][WB7]写入开始

[WB][WB7]写入结束

[WB][WB8]写入开始

[WB][WB8]写入结束

[ WB][WB9]写开始

[WB][WB9]写结束...


> go version

go version go1.5.2 windows/amd64

问题是:为什么“[WA]”的go-routine没有机会?为什么互斥体代码会停止另一个完整的 go-routine?


我知道一定有关于它的故事或理论。请给我一个阅读和学习的网址。


慕斯709654
浏览 191回答 3
3回答

慕婉清6462132

Go 使用协作式多任务处理;它不使用抢占式多任务处理:计算机多任务处理。您需要让调度程序有机会在锁之间运行。例如,通过调用 Gosched(),package mainimport (&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "runtime"&nbsp; &nbsp; "sync"&nbsp; &nbsp; "time")var m *sync.RWMutexfunc main() {&nbsp; &nbsp; m = new(sync.RWMutex)&nbsp; &nbsp; n := 100&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; for i := 0; i < n; i++ {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; write("WA", i)&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }()&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; for i := 0; i < n; i++ {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; write("WB", i)&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }()&nbsp; &nbsp; select {}}func write(tag string, i int) {&nbsp; &nbsp; m.Lock()&nbsp; &nbsp; fmt.Printf("[%s][%s%d]write start \n", tag, tag, i)&nbsp; &nbsp; time.Sleep(100 * time.Millisecond)&nbsp; &nbsp; fmt.Printf("[%s][%s%d]write end \n", tag, tag, i)&nbsp; &nbsp; m.Unlock()&nbsp; &nbsp; runtime.Gosched()}输出:[WB][WB0]write start&nbsp;[WB][WB0]write end&nbsp;[WA][WA0]write start&nbsp;[WA][WA0]write end&nbsp;[WB][WB1]write start&nbsp;[WB][WB1]write end&nbsp;[WA][WA1]write start&nbsp;[WA][WA1]write end&nbsp;[WB][WB2]write start&nbsp;[WB][WB2]write end&nbsp;[WA][WA2]write start&nbsp;[WA][WA2]write end&nbsp;[WB][WB3]write start&nbsp;[WB][WB3]write end&nbsp;[WA][WA3]write start&nbsp;[WA][WA3]write end&nbsp;

哔哔one

这种情况称为活锁。当你调用m.Unlock()即使两个 goroutines(A 和 B)正在等待这个锁被释放时,调度程序可以自由地唤醒它们中的任何一个以继续。看起来 Go 中调度程序的当前实现并没有快速切换到 goroutine A 以使其足以获取互斥锁。在这发生之前,goroutine B 重新获取互斥锁。正如您可能发现的那样,如果您在time.Sleep调用后移动调用m.UnlockA 和 B goroutines 将同时运行。希望这是有道理的。

慕村225694

只是为了详细说明调度,该for循环是一个顺序紧密循环。意味着来自该循环的编译指令将占用整个线程来完成。同时,低级指令的其他位将阻塞,除非它们在循环runtime.Gosched()中间有一些由或 sleep提供的调度周期。这就是为什么他们实际上没有机会了解sync.Mutex(顺便说一句,两者都应该sync.Mutex在声明和实例化时):go func() {&nbsp; &nbsp; for i := 0; i < n; i++ {&nbsp; &nbsp; &nbsp; &nbsp; runtime.Gosched()&nbsp; &nbsp; &nbsp; &nbsp; write("WA", i)&nbsp; &nbsp; }}()go func() {&nbsp; &nbsp; for i := 0; i < n; i++ {&nbsp; &nbsp; &nbsp; &nbsp; runtime.Gosched()&nbsp; &nbsp; &nbsp; &nbsp; write("WB", i)&nbsp; &nbsp; }}()并且 Go 调度程序在指令级别(如 Erlang)不是抢占式的。这就是为什么最好使用通道来编排执行路径的原因。注意:我通过艰难的方式(不是低级 Go 编译器专家)学到了这一点。通道以更干净的方式在 Go-Routines(和那些额外的周期)上提供编排。换句话说,sync.Mutex应该仅用于监督对事物的访问;不是为了编排。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go