Go Channel 缓冲详细介绍
什么是缓冲通道
我们在Go 语言Channel 通道详解中讨论的所有通道基本上都是无缓冲的。 正如我们在文章中详细讨论的那样,向无缓冲通道的写入和读取都是阻塞的。
可以使用缓冲区创建通道。 仅当缓冲区已满时才会阻止写入到缓冲通道。 类似地,只有当缓冲区为空时,才会阻塞从缓冲区通道进行读取。
可以通过将额外的容量参数传递给指定缓冲区大小的 make
函数来创建缓冲通道。
ch := make(chantype, capacity)
capacity 参数应大于 0 才能使通道具有缓冲区。 默认情况下,无缓冲通道的 capacity 为 0,因此我们在Go 语言Channel 通道详解中创建通道时省略了容量参数。
下面我们看一个示例
package main
import (
"fmt"
)
funcmain() {
ch := make(chanstring, 2)
ch <- "jiyik"
ch <- "onmpw"
fmt.Println(<- ch)
fmt.Println(<- ch)
}
运行示例
在上面的程序中,我们创建了一个容量为2的缓冲通道。由于通道的容量为2,因此可以在不阻塞的情况下将2个字符串写入通道。 我们将 2 个字符串写入通道。并且通道不阻塞。 然后我们读取了 2 个字符串。所以上述程序执行结果如下
让我们再看一个缓冲通道的示例,其中通道的值从并发的Goroutine中写入并从主 Goroutine 读取。 这个例子将使我们更好地理解何时写入缓冲通道块。
package main
import (
"fmt"
"time"
)
funcwrite(ch chanint) {
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("成功写入 ", i, "到通道中")
}
close(ch)
}
funcmain() {
ch := make(chanint, 2)
go write(ch)
time.Sleep(2 * time.Second)
for v := range ch {
fmt.Println("从通道中读取值 ", v)
time.Sleep(2 * time.Second)
}
}
运行示例
首先我们来看上述程序运行结果(也可以点击运行示例查看运行结果)
下面我们来分析上述结果产生的过程。
在上面的程序中,创建了一个容量为 2 的缓冲通道 ch
。 主 Goroutine 将其传递给 write Goroutine。 然后主 Goroutine 休眠 2 秒。 在此期间,write Goroutine 并发运行。 write Goroutine 有一个 for 循环,它将把 0 到 4 的数字写入 ch 通道。 这个缓冲通道的容量是 2,因此写入 Goroutine 将能够立即将值 0 和 1 写入 ch 通道,然后它会阻塞,直到从 ch 通道读取至少一个值。 所以这个程序将立即打印以下 2 行。
成功写入 0 到通道中成功写入 1 到通道中
打印完以上两行后,write Goroutine 中对 ch 通道的写入被阻塞,直到有人从 ch 通道读取数据。 由于主 Goroutine 在开始从通道读取之前会休眠 2 秒,因此程序在接下来的 2 秒内不会打印任何内容。 主 Goroutine 在 2 秒后唤醒并开始使用第 1 行的 for range 循环从 ch 通道读取。 并打印读取值,然后再次休眠 2 秒,这个循环一直持续到 ch 关闭。 所以程序会在 2 秒后打印以下几行,
从通道中读取值 0成功写入 2 到通道中
循环这个过程,一直持续到所有值都写入通道并且在write Goroutine 中关闭。
死锁
package mainimport (
"fmt"
)
funcmain() {
ch := make(chanstring, 2)
ch <- "naveen"
ch <- "paul"
ch <- "steve"
fmt.Println(<-ch)
fmt.Println(<-ch)
}
在上面的程序中,我们将 3 个字符串写入容量为 2 的缓冲通道。 由于通道已超出其容量,写入被阻塞。 现在一些 Goroutine 必须从通道读取才能继续写入,但在这种情况下,没有从该通道读取的并发例程。 因此会出现死锁,程序会在运行时出现以下消息,并发生 panic:
关闭缓冲通道
我们已经在Go 语言Channel 通道详解中讨论了如何关闭通道。 除了我们在那篇文章中学到的知识之外,在关闭缓冲通道时还有一个需要考虑的微妙之处。
可以从已经关闭的缓冲通道中读取数据。 通道将返回已经写入通道的数据,一旦所有数据都被读取,它将返回通道的零值。
下面我们通过一个示例来理解这一点
package main
import (
"fmt"
)
funcmain() {
ch := make(chanint, 5)
ch <- 5
ch <- 6
close(ch)
n, open := <-ch
fmt.Printf("Received: %d, open: %t\n", n, open)
n, open = <-ch
fmt.Printf("Received: %d, open: %t\n", n, open)
n, open = <-ch
fmt.Printf("Received: %d, open: %t\n", n, open)
}
运行示例
在上面的程序中,我们创建了一个容量为 5 的缓冲通道。 然后我们将 5 和 6 写入通道。 接着通道关闭。即使通道关闭,我们也可以读取已经写入通道的值。 n 的值将是 5 并且 open 在为真。 下一个 n 的值将是 6 并且 open 依然为真。我们现在已经完成了从通道中读取 5 和 6 ,接下来没有更多数据要读取。 现在,当再次从通道读取数据时。n 的值将为 0,即 int 的零值,open 将为假,此时表示通道已关闭。
下面我们可以通过使用 for range
来重写上面的程序
package main
import (
"fmt"
)
funcmain() {
ch := make(chanint, 5)
ch <- 5
ch <- 6
close(ch)
for n := range ch {
fmt.Println("Received:", n)
}
}
运行示例
上面程序中使用 for range 循环来读取写入通道的所有值,并且一旦由于通道已经关闭而没有更多值要读取时循环将退出。
程序执行结果如下
本文转载自:迹忆客(https://www.jiyik.com)
以上是 Go Channel 缓冲详细介绍 的全部内容, 来源链接: utcz.com/z/290242.html