为什么互斥代码会停止另一个整个例行程序?

时间:2016-03-11 04:23:56

标签: go 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)
}

控制台中的结果:

  

去运行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;? 为什么互斥代码会停止另一个整个过程?

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

3 个答案:

答案 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应仅用于监督对内容的访问;不是为了编排。