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 个字符串。所以上述程序执行结果如下

Go 缓冲通道示例运行结果

让我们再看一个缓冲通道的示例,其中通道的值从并发的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)

}

}

运行示例

首先我们来看上述程序运行结果(也可以点击运行示例查看运行结果)

Go 并发写入通道

下面我们来分析上述结果产生的过程。

在上面的程序中,创建了一个容量为 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"

)

funcmain() {

ch := make(chanstring, 2)

ch <- "naveen"

ch <- "paul"

ch <- "steve"

fmt.Println(<-ch)

fmt.Println(<-ch)

}

在上面的程序中,我们将 3 个字符串写入容量为 2 的缓冲通道。 由于通道已超出其容量,写入被阻塞。 现在一些 Goroutine 必须从通道读取才能继续写入,但在这种情况下,没有从该通道读取的并发例程。 因此会出现死锁,程序会在运行时出现以下消息,并发生 panic:

Go 死锁


关闭缓冲通道

我们已经在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 将为假,此时表示通道已关闭。

Go 关闭通道

下面我们可以通过使用 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 循环来读取写入通道的所有值,并且一旦由于通道已经关闭而没有更多值要读取时循环将退出。

程序执行结果如下

go 关闭通道示例2

本文转载自:迹忆客(https://www.jiyik.com)

以上是 Go Channel 缓冲详细介绍 的全部内容, 来源链接: utcz.com/z/290242.html

回到顶部