3 回答
TA贡献1804条经验 获得超2个赞
Go 使用协作式多任务处理;它不使用抢占式多任务处理:计算机多任务处理。您需要让调度程序有机会在锁之间运行。例如,通过调用 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
TA贡献1854条经验 获得超8个赞
这种情况称为活锁。
当你调用m.Unlock()
即使两个 goroutines(A 和 B)正在等待这个锁被释放时,调度程序可以自由地唤醒它们中的任何一个以继续。
看起来 Go 中调度程序的当前实现并没有快速切换到 goroutine A 以使其足以获取互斥锁。在这发生之前,goroutine B 重新获取互斥锁。
正如您可能发现的那样,如果您在time.Sleep
调用后移动调用m.Unlock
A 和 B goroutines 将同时运行。
希望这是有道理的。
TA贡献1880条经验 获得超4个赞
只是为了详细说明调度,该for循环是一个顺序紧密循环。意味着来自该循环的编译指令将占用整个线程来完成。同时,低级指令的其他位将阻塞,除非它们在循环runtime.Gosched()中间有一些由或 sleep提供的调度周期。这就是为什么他们实际上没有机会了解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应该仅用于监督对事物的访问;不是为了编排。
- 3 回答
- 0 关注
- 190 浏览
添加回答
举报