为什么在Go语言中,通道(channel)需要使用goroutine?

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

Why does a channel in golang require a go-routine?

问题

我正在学习Go语言中的通道。根据它的文档

> 通道是一种类型化的管道,通过它你可以使用通道操作符<-发送和接收值。

我理解了这一点。我从使用Go协程的示例中了解了它的用法。我尝试了一个非常简单的例子,但结果是一个死锁的程序。忽略这个程序毫无意义的一点,你能告诉我为什么会发生死锁吗?

package main
import "fmt"
func main() {
    c := make(chan int)
    c <- 17
    fmt.Println(<-c)
}

上述文档还指出,

> 默认情况下,发送和接收操作会阻塞,直到另一侧准备好。

好的,在上面的例子中,发送方(主协程)在遇到c <- 17时是准备好发送的。所以它不应该执行吗?随后的Println应该能够从通道中读取值。

我意识到,如果将c <- 17替换为

go func() { c <- 17 } ()

一切都能正常工作。我只是想弄清楚为什么需要这样做。

英文:

I am coming upto speed on channels in golang. Per its documentation,

> Channels are a typed conduit through which you can send and receive
> values with the channel operator, <-.

I get that. I understand how it is used from examples that utilize go routines. I tried an extremely trivial example. It results in a deadlocked program. Ignoring the pointlessness of this program can you please tell me why this is deadlocked?

package main
import	&quot;fmt&quot;
func main() {
	c := make(chan int)
	c &lt;- 17
	fmt.Println(&lt;- c)
}

The referenced documentation adds that

> By default, sends and receives block until the other side is ready.

OK, in the above example, the sender (the main routine) is ready to send when c &lt;- 17 is encountered. So shouldn't that execute. Subsequently the Println should be able to drain the channel.

I realize everything works fine if c &lt;- 17 is replaced by

go func() { c &lt;- 17 } ()

Just trying to understand why that's necessary.

答案1

得分: 16

> Go编程语言规范
>
> 通道类型
>
> 通道提供了一种机制,用于并发执行的函数通过发送和接收指定元素类型的值来进行通信。
>
> 可以使用内置函数make创建一个新的、初始化的通道值,该函数接受通道类型和可选容量作为参数:
>
> make(chan int, 100)
>
> 容量(以元素数量表示)设置了通道中缓冲区的大小。如果容量为零或不存在,则通道是无缓冲的,只有在发送方和接收方都准备好时通信才会成功。否则,通道是有缓冲的,如果缓冲区不满(发送)或不空(接收),通信将成功而不会阻塞。空通道永远不会准备好进行通信。


你问:为什么在golang中通道需要一个go程?

Go语言中的通道并不需要一个go程。成功的发送只需要一个准备好的接收方或者一个未满的缓冲区。例如,使用一个有缓冲区的通道:

package main

import "fmt"

func main() {
    c := make(chan int, 1)
    c <- 17
    fmt.Println(<-c)
}

输出:

17

你的示例失败是因为它试图在一个没有准备好的接收方的无缓冲通道上发送:

package main

import "fmt"

func main() {
    c := make(chan int)
    c <- 17
    fmt.Println(<-c)
}

输出:

fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
    /home/peter/gopath/src/sri.go:7 +0x59

@Tim Cooper提出的解决方案也有相同的错误:

package main

import "fmt"

func main() {
    c := make(chan int)
    select {
    case c <- 17:
    default:
    }
    fmt.Println(<-c)
}

输出:

fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
    /home/peter/gopath/src/tim.go:11 +0x8a
英文:

> 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.
>
> 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.


You ask: Why does a channel in golang require a go-routine?

A Go channel does not require a goroutine. A successful send merely requires a ready receiver or a buffer that is not full. For example, using a buffered channel,

package main

import &quot;fmt&quot;

func main() {
	c := make(chan int, 1)
	c &lt;- 17
	fmt.Println(&lt;-c)
}

Output:

17

Your example fails because it tries to send on an unbuffered channel that does not have a ready receiver.

package main

import &quot;fmt&quot;

func main() {
	c := make(chan int)
	c &lt;- 17
	fmt.Println(&lt;-c)
}

Output:

fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
    /home/peter/gopath/src/sri.go:7 +0x59

@Tim Cooper's proposed solution has the same error.

package main

import &quot;fmt&quot;

func main() {
	c := make(chan int)
	select {
	case c &lt;- 17:
	default:
	}
	fmt.Println(&lt;-c)
}

Output:

fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
    /home/peter/gopath/src/tim.go:11 +0x8a

答案2

得分: 10

默认情况下,发送和接收操作会阻塞,直到另一方准备好。

确切地说,由于没有goroutine在等待接收操作,发送操作被阻塞,导致程序发生死锁。发送操作不会被跳过,因为没有人在等待接收。

如果你想进行非阻塞的发送操作,可以在select语句中使用发送操作符和default语句:

select {
case c <- 17:
default:
}
英文:

> By default, sends and receives block until the other side is ready.

Exactly: since no go routine is waiting to receive, the send is blocked, and your program deadlocks. The send operation does not get skipped over because no one is waiting to receive.

If you want to do a non-blocking send, you would use the send operator in a select statement with a default case:

select {
case c &lt;- 17:
default:
}

huangapple
  • 本文由 发表于 2017年5月27日 19:44:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/44216442.html
匿名

发表评论

匿名网友

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

确定