本文为翻译文章
什么是缓冲通道?
在我们前面章节提到过的 channel 基本上都是无缓冲通道。正如我们在通道教程中详细讨论的那样,从无缓冲通道发送和接收数据都处于阻塞状态。
我们可以创建一个有缓冲区的通道。发送数据的时候只有在缓冲区满的时候才会阻塞。同样的,接收数据的时候只有在缓冲区为空的时候才阻塞。
可以通过向 make 函数传递一个附加的容量参数来创建缓冲的通道,该参数指定缓冲区的大小。
ch := make(chan type, capacity)
上面的语法中的容量应大于 0,以使通道具有缓冲区。无缓冲区通道的容量为 0 ,因此为们前面的文章中创建通道时省略了这个参数。
例子
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 2)
ch <- "naveen"
ch <- "paul"
fmt.Println(<- ch)
fmt.Println(<- ch)
}
上面代码中,为们创建了一个容量为 2 的缓冲通道。由于通道的容量为 2,因此可以在不阻塞的情况下将 2 个字符串写入通道。我们写入了 naveen 和 paul 两个字符串到通道,并且通道不会阻塞。然后从通道读取它们。
程序输出:
naveen
paul
另外的例子
让我们来看下另外的一个例子,这个例子是在一个 Goroutine 中向缓冲通道写入数据,同时在主 Goroutine 中读取该通道数据。这个例子能够帮助我们更好的理解缓冲通道。
package main
import (
"fmt"
"time"
)
func write(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("successfully wrote", i, "to ch")
}
close(ch)
}
func main() {
ch := make(chan int, 2)
go write(ch)
time.Sleep(2 * time.Second)
for v := range ch {
fmt.Println("read value", v,"from ch")
time.Sleep(2 * time.Second)
}
}
在上面代码中,一个容量为 2 的缓冲通道被创建,并在主 Goroutine 中传递给 write Goroutine。然后主 Goroutine 睡眠 2 秒钟。在这期间,write Goroutine 也在同时运行着。write Goroutine 用 for 循环向 ch 通道写入了数字 0-4。由于通道的容量只有 2 大小,因此 write Goroutine 只能向通道写入 2 个数据,然后在通道至少被读取一个数据之前将会被阻塞。
程序输出:
successfully wrote 0 to ch
successfully wrote 1 to ch
在打印两行数据之后,write Goroutine 在 ch 通道被数据数据之前将会一直被阻塞。由于主 Goroutine 在开始从通道读取之前会休眠 2 秒钟,因此该程序将在接下来的 2 秒钟内不打印任何内容。在休眠 2 秒中后,主 Goroutine 开始用 for range 来取ch 通道里的数据,打印出读取的数据后再休眠 2 秒钟,一直循环直到 ch 通道关闭为止。因此,该程序将在2秒后打印以下行:
read value 0 from ch
successfully wrote 2 to ch
一直循环,直到所有值都写入通道,并且在write Goroutine 中将其关闭为止。最终的输出将是:
successfully wrote 0 to ch
successfully wrote 1 to ch
read value 0 from ch
successfully wrote 2 to ch
read value 1 from ch
successfully wrote 3 to ch
read value 2 from ch
successfully wrote 4 to ch
read value 3 from ch
read value 4 from ch
死锁
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 2)
ch <- "naveen"
ch <- "paul"
ch <- "steve"
fmt.Println(<-ch)
fmt.Println(<-ch)
}
在上面的程序中,我们将 3 个字符串写入容量为 2 的缓冲通道中。当第三个写入时,由于通道已超出容量,写入被阻止。现在,必须从通道读取一些 Goroutine 才能使写入继续进行,但是在这种情况下,没有从该通道读取并发例程。因此,将出现死锁,并且程序将在运行时发生 panic。
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/tmp/sandbox091448810/prog.go:11 +0x8d
长度与容量
缓冲通道的容量是通道可以容纳的值的数量。这是我们在使用 make 函数创建缓冲通道时指定的值。
缓冲通道的长度是当前在其中排队的元素数。
一个程序可以使事情变得清晰😀
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 3)
ch <- "naveen"
ch <- "paul"
fmt.Println("capacity is", cap(ch))
fmt.Println("length is", len(ch))
fmt.Println("read value", <-ch)
fmt.Println("new length is", len(ch))
}
在上面代码中,我们创建了一个 容量为 3 的通道,它能容纳 3 个字符串。然后我们在接下来的代码中向通道写入 2 个字符串。现在通道里有 2 个字符串在排队,因此通道的长度为 2。然后我们从通道里读取了一个数据。现在通道里只有一个字符串在排队,因此通道的长度为 1。
程序输出:
capacity is 3
length is 2
read value naveen
new length is 1