Golang中的入站通道在goroutine内部无法接收。

huangapple go评论79阅读模式
英文:

Golang inbound channel not receiving inside a goroutine

问题

请帮助我理解为什么在这种情况下入站的<-done通道没有被接收到?

func main() {
    done := make(chan bool)
    println("enter")
    defer func() {
        println("exit")
    }()
    defer func() {
        println("  notify start")
        done <- true
        println("  notify end")
    }()     
    go func() {
        println("    wait start")
        <-done
        println("    wait end")
    }()
    time.Sleep(time.Millisecond) // << when this is removed, it works.
}

我期望的输出是:

enter
  notify start
    wait start
    wait end
  notify end
exit

但实际输出是:

enter
    wait start
  notify start
  notify end
exit

我最初认为done通道可能在早期被关闭或清理,但即使done是全局的,结果仍然是相同的意外行为。

难道<-done不应该阻塞直到done <- true发生吗?反之亦然?

解决方案

似乎我期望程序在所有goroutine完成之前等待退出。这是一个错误的假设。

这是一个不太优雅的解决方法:

func main() {
    done, reallydone := make(chan bool), make(chan bool)
    println("enter")
    defer func() {
        <-reallydone
        println("exit")
    }()
    go func() {
        println("    wait start")
        <-done
        println("    wait end")
        reallydone <- true
    }()
    defer func() {
        println("  notify start")
        done <- true
        println("  notify end")
    }()
    time.Sleep(time.Millisecond)
}
英文:

Please help me understand why the inbound &lt;-done channel is not being picked up in this case?

func main() {
    done := make(chan bool)
    println(&quot;enter&quot;)
    defer func() {
        println(&quot;exit&quot;)
    }()
    defer func() {
        println(&quot;  notify start&quot;)
        done &lt;- true
        println(&quot;  notify end&quot;)
    }()     
    go func() {
        println(&quot;    wait start&quot;)
        &lt;-done
        println(&quot;    wait end&quot;)
    }()
    time.Sleep(time.Millisecond) // &lt;&lt; when this is removed, it works.
}

I'm expecting the output to be:

enter
  notify start
    wait start
    wait end
  notify end
exit

But instead it's:

enter
    wait start
  notify start
  notify end
exit

I initially assumed that the done channel was somehow being closed or cleaned up early, but it results in the same unexpected behavior even when the done is global.

Shouldn't &lt;-done block until done &lt;- true occurs? And vice versa?

Resolution

It seems that I was expecting the program to wait for all of the goroutines to completed before exiting. This is an incorrect assumption.

Here's an dirty workaround:

func main() {
    done, reallydone := make(chan bool), make(chan bool)
    println(&quot;enter&quot;)
    defer func() {
        &lt;-reallydone
        println(&quot;exit&quot;)
    }()
    go func() {
        println(&quot;    wait start&quot;)
        &lt;-done
        println(&quot;    wait end&quot;)
        reallydone &lt;- true
    }()
    defer func() {
        println(&quot;  notify start&quot;)
        done &lt;- true
        println(&quot;  notify end&quot;)
    }()
    time.Sleep(time.Millisecond)
}

答案1

得分: 2

当你使用sleep函数时,它会给goroutine一些时间来启动,然后在它从通道中读取之前,主函数就已经退出了,所以最后的println(" wait end")没有被调用。

然而,如果你不调用sleep函数,defer语句将会阻塞,直到goroutine从通道中读取数据,这给它足够的时间来打印输出。

如果你将这段代码移到一个不同的函数中,并从主函数中调用它,它将按预期工作。

func stuff() {
    done := make(chan bool)
    println("enter")
    defer func() {
        println("exit")
    }()
    go func() {
        println("    wait start")
        <-done
        println("    wait end")
    }()
    defer func() {
        println("  notify start")
        done <- true
        println("  notify end")
    }()
}

func main() {
    stuff()
}
英文:

When you use sleep, it gives time for the goroutine to start, then by the time it reads from the channel, main exits before the last println(&quot; wait end&quot;) gets called.

However if you don't call sleep, defer will block until the goroutine reads from it and that gives it enough time to print.

If you move the code to a different function and call it from main it will work as expected.

func stuff() {
	done := make(chan bool)
	println(&quot;enter&quot;)
	defer func() {
		println(&quot;exit&quot;)
	}()
	go func() {
		println(&quot;    wait start&quot;)
		&lt;-done
		println(&quot;    wait end&quot;)
	}()
	defer func() {
		println(&quot;  notify start&quot;)
		done &lt;- true
		println(&quot;  notify end&quot;)
	}()
}
func main() {
	stuff()
}

答案2

得分: 1

done通道上的事件顺序是:“当通道是无缓冲的时候,只有在发送方和接收方都准备好时,通信才会成功。”对于你的示例,

done通道是无缓冲的:main

done := make(chan bool)

接收等待发送:go func()

<-done 

接收准备好(<-done),发送:defer func()

done <- true

main函数结束后不等待goroutine(go func())完成。

输出:

进入
    等待开始
  通知开始
  通知结束
退出

Go编程语言规范

通道类型

通道提供了一种机制,用于并发执行函数之间通过发送和接收指定元素类型的值进行通信。未初始化的通道的值为nil。

可以使用内置函数make创建一个新的初始化的通道值,该函数接受通道类型和可选的容量作为参数:

make(chan int, 100)

容量(以元素数量表示)设置通道中缓冲区的大小。如果容量为零或不存在,则通道是无缓冲的,只有在发送方和接收方都准备好时通信才会成功。否则,通道是有缓冲的,如果缓冲区不满(发送)或不空(接收),通信将成功而不会阻塞。nil通道永远不会准备好进行通信。

Go语句

程序的执行从初始化主包开始,然后调用main函数。当该函数调用返回时,程序退出。它不会等待其他(非主)goroutine 完成。

英文:

The sequence of events on the done channel: "[when] the channel is unbuffered communication succeeds only when both a sender and receiver are ready." For your example,

The done channel is unbuffered: main.

done := make(chan bool)

Receive waiting for a send: go func().

&lt;-done 

Receive ready (&lt;-done), send: defer func().

done &lt;- true

The main function ends and does not wait for the goroutine (go func()) to finish.

Output:

enter
    wait start
  notify start
  notify end
exit

> The Go Programming Language
> Specification

>
> Channel types
>
> A channel provides a mechanism for concurrently executing functions to
> communicate by sending and receiving values of a specified element
> type. The value of an uninitialized channel is nil.
>
> A new, initialized channel value can be made using the built-in
> function make, which takes the channel type and an optional capacity
> as arguments:
>
> make(chan int, 100)
>
> The capacity, in number of elements, sets the size of the buffer in
> the channel. If the capacity is zero or absent, the channel is
> unbuffered and communication succeeds only when both a sender and
> receiver are ready. Otherwise, the channel is buffered and
> communication succeeds without blocking if the buffer is not full
> (sends) or not empty (receives). A nil channel is never ready for
> communication.
>
> Go statements
>
> Program execution begins by initializing the main package and then
> invoking the function main. When that function invocation returns, the
> program exits. It does not wait for other (non-main) goroutines to
> complete.

huangapple
  • 本文由 发表于 2014年8月16日 22:20:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/25341047.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定