Go教程-缓冲通道|Go主题月

186 阅读4分钟

本文为翻译文章

原文地址:golangbot.com/buffered-ch…

什么是缓冲通道?

在我们前面章节提到过的 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 个字符串写入通道。我们写入了 naveenpaul 两个字符串到通道,并且通道不会阻塞。然后从通道读取它们。

程序输出:

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