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)
}
控制台中的结果:
去运行mutex.go
[WB] [WB0]写入开始
[WB] [WB0]写入结束
[WB] [WB1]写入开始
[WB] [WB1]写入结束
[WB] [WB2]写入开始
[WB] [WB2]写入结束
[WB] [WB3]写入开始
[WB] [WB3]写入结束
[WB] [WB4]写入开始
[WB] [WB4]写入结束
[WB] [WB5]写入开始
[WB] [WB5]写入结束
[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
问题是: 为什么没有机会进行&#34; [WA]&#34;? 为什么互斥代码会停止另一个整个过程?
我知道必须有一个关于它的故事或理论。 请给我一个阅读和学习的网址。
答案 0 :(得分:2)
这种情况称为实时锁定。
当您调用m.Unlock()
时,即使两个goroutine(A和B)正在等待释放此锁,调度程序也可以自由唤醒其中任何一个以继续。
看起来Go中调度程序的当前实现并没有快速切换到goroutine A来获取互斥锁。在此之前,goroutine B重新获得了互斥锁。
您可能已经发现,如果您在time.Sleep
之后调用m.Unlock
,则会同时调用A和B goroutines。
希望这是有道理的。
答案 1 :(得分:2)
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
答案 2 :(得分:1)
@peterSO的回答是正确的。为了详细说明调度,for
循环是一个顺序紧密循环。意味着来自该循环的编译指令将占用整个线程来完成。同时,低级指令的其他位将被阻塞,除非它们在循环的中间具有由runtime.Gosched()
或睡眠提供的一些调度周期。这就是为什么他们实际上没有机会接受sync.Mutex
(BTW在声明和实例化时都应该是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
应仅用于监督对内容的访问;不是为了编排。