Go Channel 缓冲详细介绍
什么是缓冲通道
我们在Go 语言Channel 通道详解中讨论的所有通道基本上都是无缓冲的。 正如我们在文章中详细讨论的那样,向无缓冲通道的写入和读取都是阻塞的。
可以使用缓冲区创建通道。 仅当缓冲区已满时才会阻止写入到缓冲通道。 类似地,只有当缓冲区为空时,才会阻塞从缓冲区通道进行读取。
可以通过将额外的容量参数传递给指定缓冲区大小的 make
函数来创建缓冲通道。
ch := make(chan type, capacity)
capacity 参数应大于 0 才能使通道具有缓冲区。 默认情况下,无缓冲通道的 capacity 为 0,因此我们在Go 语言Channel 通道详解中创建通道时省略了容量参数。
下面我们看一个示例
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 2)
ch <- "jiyik"
ch <- "onmpw"
fmt.Println(<- ch)
fmt.Println(<- ch)
}
在上面的程序中,我们创建了一个容量为2的缓冲通道。由于通道的容量为2,因此可以在不阻塞的情况下将2个字符串写入通道。 我们将 2 个字符串写入通道。并且通道不阻塞。 然后我们读取了 2 个字符串。所以上述程序执行结果如下
让我们再看一个缓冲通道的示例,其中通道的值从并发的Goroutine中写入并从主 Goroutine 读取。 这个例子将使我们更好地理解何时写入缓冲通道块。
package main
import (
"fmt"
"time"
)
func write(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("成功写入 ", i, "到通道中")
}
close(ch)
}
func main() {
ch := make(chan int, 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 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:
关闭缓冲通道
我们已经在Go 语言Channel 通道详解中讨论了如何关闭通道。 除了我们在那篇文章中学到的知识之外,在关闭缓冲通道时还有一个需要考虑的微妙之处。
可以从已经关闭的缓冲通道中读取数据。 通道将返回已经写入通道的数据,一旦所有数据都被读取,它将返回通道的零值。
下面我们通过一个示例来理解这一点
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 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"
)
func main() {
ch := make(chan int, 5)
ch <- 5
ch <- 6
close(ch)
for n := range ch {
fmt.Println("Received:", n)
}
}
上面程序中使用 for range 循环来读取写入通道的所有值,并且一旦由于通道已经关闭而没有更多值要读取时循环将退出。
程序执行结果如下
相关文章
使用 C 语言中的 goto 语句
发布时间:2023/05/07 浏览次数:79 分类:C语言
-
本文介绍了如何在 C 语言中使用 goto 语句。使用 goto 语句在 C 语言中实现循环 goto 关键字是 C 语言的一部分,它提供了一个做无条件跳转的结构。
Django 中的 Slug
发布时间:2023/05/04 浏览次数:173 分类:Python
-
本篇文章旨在定义一个 slug 以及我们如何使用 slug 字段在 Python 中使用 Django 获得独特的帖子。
在 Django 中按降序过滤查询集中的项目
发布时间:2023/05/04 浏览次数:157 分类:Python
-
在这个讲解中,学习如何借助 Django 中的 order_by() 方法按降序过滤出查询集中的项目。
Django ALLOWED_HOSTS 介绍
发布时间:2023/05/04 浏览次数:181 分类:Python
-
本文展示了如何创建您的 Django 网站,为公开发布做好准备,如何设置 ALLOWED_HOSTS 以及如何在使用 Django 进行 Web 部署期间修复预期的主要问题。
Django 中的 Select_related 方法
发布时间:2023/05/04 浏览次数:129 分类:Python
-
本文介绍了什么是查询集,如何处理这些查询以及我们如何利用 select_related() 方法来过滤 Django 中相关模型的查询。
使用 Post 请求将数据发送到 Django 服务器
发布时间:2023/05/04 浏览次数:159 分类:Python
-
在这篇关于Django的讲解中,我们简要介绍了post和get请求以及如何在Django中用post实现CSRF token。
Django 返回 JSON
发布时间:2023/05/04 浏览次数:106 分类:Python
-
在与我们的讨论中,我们简要介绍了 JSON 格式,并讨论了如何借助 Django 中的 JsonResponse 类将数据返回为 JSON 格式。
在 Django 中创建对象
发布时间:2023/05/04 浏览次数:59 分类:Python
-
本文的目的是解释什么是模型以及如何使用 create() 方法创建对象,并了解如何在 Django 中使用 save() 方法。