c:=make(chan int) 和 c:=make(chan int,1) 有什么区别?

7

我认为它们是相同的,但Go内存模型中有这样一句话:如果通道是带缓冲的(例如:c = make(chan int, 1)),那么程序就不能保证打印“hello, world” - 它可能会打印空字符串、崩溃或做其他事情。 这个说法正确吗?


非常相似:https://dev59.com/NWIj5IYBdhLWcg3wpWlx - Matt
3个回答

20

虽然 Evan 是正确的,但我认为更详细的解释可能会有用:

Effective Go 中所述,以下两种方式等价,并且都会得到 无缓冲通道

ci := make(chan int)            // unbuffered channel of integers
cj := make(chan int, 0)         // unbuffered channel of integers

如果拥有任何其他值,那么将会得到一个带缓冲的通道

ck := make(chan int, 1)         // buffered channel of integers

缓冲通道

使用缓冲通道,一个 Go 协程可以将一个值放入通道中 (ck <- 42),然后继续执行下一条指令,而无需等待某个协程从通道中读取。除非通道缓冲区已满,否则都是如此。

如果通道已满,该 Go 协程将等待另一个 Go 协程从通道中读取值,然后才能将自己的值放入其中。

非缓冲通道

非缓冲通道没有存储任何数据的空间。因此,为了传递一个值到非缓冲通道,发送Go协程将会阻塞,直到接收Go协程收到该值。

因此,缓冲通道和非缓冲通道之间肯定存在差异。在内存模型上也是如此:

package main
import "fmt"

var c = make(chan int)
var a string

func f() {
    a = "hello, world"
    x := <- c
    fmt.Println(x)
}

func main() {
    go f()
    c <- 0
    print(a)
}

如果你有一个带缓冲区的通道 var c = make(chan int, 1),则主 Go 协程只需将一个值放入缓冲区,然后继续执行 print(a),可能在 f() Go 协程设置 a"hello,world"之前。

但在当前代码中,主要的 Go 协程将在 c < -0 处阻塞,等待 f() 接收值后才继续打印,然后我们可以确定 a 已经设置为 "hello,world"

PlaygroundLink-WithBuffer

PlaygroundLink-WithoutBuffer


当你说:将一个值放入通道(ck <- 42)并继续执行下一条指令而无需等待某人读取通道。因此,put Goroutineget Goroutine*同时访问单缓冲区通道。通道是一个队列。这两个Goroutines需要同步机制来访问缓冲区通道吗? - overexchange
@overexchange 可以通过多个 goroutine 在通道上进行发送和接收,而无需进一步同步。请参见最后一段:https://golang.org/ref/spec#Channel_types - ANisus

4

1
第一个创建的是无缓冲通道,而第二个创建的是带缓冲通道。
当尝试在通道上发送值时,您可以看到它们之间的区别:
c <- 1

在非缓冲通道的情况下,此语句将阻塞直到通道上发生相应的接收操作(即<-c)。在缓冲通道的情况下,如果通道的缓冲区中有足够的空间存储值,则发送可以完成而不会阻塞。
在大多数情况下,非缓冲通道是合适的,并且由于其阻塞性质,它们很快就会显示出任何并发问题的优点。但是,在某些情况下,使用缓冲区是有意义的。
就Go内存模型而言,对于缓冲和非缓冲通道,同样存在“happens before”关系。虽然使用缓冲通道时发送操作不会阻塞等待相应的接收,但在接收之后运行的代码仍然保证在发送之前运行的代码之后运行。

我想加入我的“经验法则”,即先开发没有缓冲的代码,然后再添加缓冲以提高性能。如果未缓冲的代码从不出现死锁,那么缓冲的代码也不会出现,但它可能会运行得更快。 - Rick-777

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接