为什么互斥代码会停止另一个完整的 go-routine?
Why the mutex code stops another whole 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]write start
[WB][WB0]write end
[WB][WB1]write start
[WB][WB1]write end
[WB][WB2]write start
[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]write start
[WB][WB6]write end
[WB][WB7]write start
[WB][WB7]write end
[WB][WB8]write start
[WB][WB8]write end
[WB][WB9]write start
[WB][WB9]write end
...
> go version
go version go1.5.2 windows/amd64
问题是:
为什么“[WA]”的 go-routine 没有机会?
为什么互斥代码会停止另一个完整的 go-routine?
我知道一定有故事或理论。
请给我一个url阅读学习
这种情况称为活锁。
当您调用 m.Unlock()
时,即使两个 goroutine(A 和 B)正在等待释放此锁,调度程序仍可以自由唤醒它们中的任何一个以继续。
看起来 Go 中调度程序的当前实现没有足够快地切换到 goroutine A 以使其获取互斥锁。在此之前,goroutine B 重新获取互斥体。
正如您可能发现的那样,如果您在 m.Unlock
调用之后移动 time.Sleep
调用,A 和 B goroutines 将同时 运行。
希望这是有道理的。
Go 使用协作式多任务;它不使用抢占式多任务处理:Computer multitasking。您需要给调度程序一个机会 运行 在锁之间。例如,通过调用 Gosched(),
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
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()
runtime.Gosched()
}
输出:
[WB][WB0]write start
[WB][WB0]write end
[WA][WA0]write start
[WA][WA0]write end
[WB][WB1]write start
[WB][WB1]write end
[WA][WA1]write start
[WA][WA1]write end
[WB][WB2]write start
[WB][WB2]write end
[WA][WA2]write start
[WA][WA2]write end
[WB][WB3]write start
[WB][WB3]write end
[WA][WA3]write start
[WA][WA3]write end
@peterSO 的回答是正确的。只是详细说明一下调度,for
循环是一个顺序紧密循环。意味着来自该循环的编译指令将占用整个线程来完成。同时,低级指令的其他位将阻塞,除非它们在循环中间有 runtime.Gosched()
或睡眠提供的一些调度周期。这就是为什么他们实际上没有机会了解 sync.Mutex
(顺便说一句,在声明和实例化时两者都应该是 sync.Mutex
):
go func() {
for i := 0; i < n; i++ {
runtime.Gosched()
write("WA", i)
}
}()
go func() {
for i := 0; i < n; i++ {
runtime.Gosched()
write("WB", i)
}
}()
并且 Go 调度程序在指令级别(如 Erlang)不是抢占式的。这就是为什么最好使用通道来编排执行路径。
注意:我是通过艰难的方式学到的(不是低级 Go 编译器专家)。通道以更干净的方式在 Go-Routines(以及那些额外的周期)上提供编排。换句话说,sync.Mutex
应该只用于监督对内容的访问;不用于编排。
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]write start
[WB][WB0]write end
[WB][WB1]write start
[WB][WB1]write end
[WB][WB2]write start
[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]write start
[WB][WB6]write end
[WB][WB7]write start
[WB][WB7]write end
[WB][WB8]write start
[WB][WB8]write end
[WB][WB9]write start
[WB][WB9]write end ...
> go version
go version go1.5.2 windows/amd64
问题是: 为什么“[WA]”的 go-routine 没有机会? 为什么互斥代码会停止另一个完整的 go-routine?
我知道一定有故事或理论。 请给我一个url阅读学习
这种情况称为活锁。
当您调用 m.Unlock()
时,即使两个 goroutine(A 和 B)正在等待释放此锁,调度程序仍可以自由唤醒它们中的任何一个以继续。
看起来 Go 中调度程序的当前实现没有足够快地切换到 goroutine A 以使其获取互斥锁。在此之前,goroutine B 重新获取互斥体。
正如您可能发现的那样,如果您在 m.Unlock
调用之后移动 time.Sleep
调用,A 和 B goroutines 将同时 运行。
希望这是有道理的。
Go 使用协作式多任务;它不使用抢占式多任务处理:Computer multitasking。您需要给调度程序一个机会 运行 在锁之间。例如,通过调用 Gosched(),
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
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()
runtime.Gosched()
}
输出:
[WB][WB0]write start
[WB][WB0]write end
[WA][WA0]write start
[WA][WA0]write end
[WB][WB1]write start
[WB][WB1]write end
[WA][WA1]write start
[WA][WA1]write end
[WB][WB2]write start
[WB][WB2]write end
[WA][WA2]write start
[WA][WA2]write end
[WB][WB3]write start
[WB][WB3]write end
[WA][WA3]write start
[WA][WA3]write end
@peterSO 的回答是正确的。只是详细说明一下调度,for
循环是一个顺序紧密循环。意味着来自该循环的编译指令将占用整个线程来完成。同时,低级指令的其他位将阻塞,除非它们在循环中间有 runtime.Gosched()
或睡眠提供的一些调度周期。这就是为什么他们实际上没有机会了解 sync.Mutex
(顺便说一句,在声明和实例化时两者都应该是 sync.Mutex
):
go func() {
for i := 0; i < n; i++ {
runtime.Gosched()
write("WA", i)
}
}()
go func() {
for i := 0; i < n; i++ {
runtime.Gosched()
write("WB", i)
}
}()
并且 Go 调度程序在指令级别(如 Erlang)不是抢占式的。这就是为什么最好使用通道来编排执行路径。
注意:我是通过艰难的方式学到的(不是低级 Go 编译器专家)。通道以更干净的方式在 Go-Routines(以及那些额外的周期)上提供编排。换句话说,sync.Mutex
应该只用于监督对内容的访问;不用于编排。