在通道上阻塞会发送错误的同步范例以及原因

Tob*_*bia 7 synchronization channel go goroutine

Effective Go给出了关于如何使用通道模拟信号量的示例:

var sem = make(chan int, MaxOutstanding)

func handle(r *Request) {
    <-sem
    process(r)
    sem <- 1
}

func init() {
    for i := 0; i < MaxOutstanding; i++ {
        sem <- 1
    }
}

func Serve(queue chan *Request) {
    for {
        req := <-queue
        go handle(req)
    }
}
Run Code Online (Sandbox Code Playgroud)

它还说:由于数据同步发生在来自通道的接收上(即发送"发生在接收之前;请参阅Go Memory模型),信号量的获取必须在通道接收上,而不是发送.

现在,我想我理解Go Memory Model和"之前发生过"的定义.但我没有看到阻止频道发送的问题:

func handle(r *Request) {
    sem <- 1
    process(r)
    <-sem
}

func init() {}
Run Code Online (Sandbox Code Playgroud)

此代码(具有semServe从上述不变)使用以相反的方式被缓冲的信道.频道开始为空.在进入时handle,如果已经有MaxOutstandinggoroutines正在执行该过程,则发送将被阻止.只要其中一个完成其处理并从通道"释放"一个槽,通过接收一个int,我们的发送将被解除阻塞,goroutine将开始自己的处理.

为什么这是一种不好的同步方式,因为教科书似乎意味着什么?

是否接收通道,其将释放通道插槽操作将使用相同的插槽发送"之前发生"?这怎么可能?


换句话说,语言参考"在缓冲通道上发送[阻塞直到]缓冲区中有空间."

但是内存模型只表示"来自无缓冲通道的接收发生在该通道上的发送完成之前".特别是,它并未说明在该通道上的发送完成之前,来自已满缓冲通道的接收发生.

这个角落的情况是不是可以信赖做正确的事吗?(这实际上是同步一个被阻止的发送与取消阻止它的接收)

如果是这种情况,它看起来像一种令人讨厌的竞争条件,旨在最大限度地减少鬼鬼祟祟的竞争条件:-(

var c = make(chan int, 1)
var a string

func f() {
    a = "hello, world"
    <-c  // unblock main, which will hopefully see the updated 'a'
}

func main() {
    c <- 0  // fill up the buffered channel
    go f()
    c <- 0  // this blocks because the channel is full
    print(a)
}
Run Code Online (Sandbox Code Playgroud)

小智 5

这篇Effective Go文档也让我感动.实际上,在相对最新版本的Effective Go中,所讨论的代码在通道发送上获取了信号量(而不是像当前版本中那样使用init()来"填充"通道).

显然有很多关于这个主题的讨论.我不打算总结一切,但讨论都可以从这里找到:

https://code.google.com/p/go/issues/detail?id=5023

它确实令我感到不幸,但引用该问题的报道者,短篇小说似乎是除非信号量是在频道上获得的......:

以下代码:

func handle(r *Request) {
    sem <- 1    // Wait for active queue to drain.
    process(r)  // May take a long time.
    <-sem       // Done; enable next request to run.
}
Run Code Online (Sandbox Code Playgroud)

......可以合法地"优化"成:

func handle(r *Request) {
    process(r)  // May take a long time.
    sem <- 1    // Wait for active queue to drain.
    <-sem       // Done; enable next request to run.
}
Run Code Online (Sandbox Code Playgroud)

......或者:

func handle(r *Request) {
    sem <- 1    // Wait for active queue to drain.
    <-sem       // Done; enable next request to run.
    process(r)  // May take a long time.
}
Run Code Online (Sandbox Code Playgroud)