带缓存的管道(buffered channel)

上一节我们讨论的channel都是不带缓存的,这里所谓的不带缓存的意思是写进去一个数据,就必须先读取走才能接着写下一个数据进去,因此也是阻塞的。

Golang中我们还可以创建带缓存的channel,向带缓存的channel中写数据时,只有写满缓存才会阻塞协程的执行;同样的,从带缓存的channel中读数据时,只有缓存为空时,即已经没数据可读时才会阻塞协程的执行。

可以通过如下语法来创建带缓存的channel

ch := make(chan type, capacity)

这里的capacity表示缓存容量,必须大于或等于1才有缓存,默认的不带缓存的channel缓存容量为0。

带缓存channel举例

我们举个例子来体会下带缓存channel的用法。

package main

import "fmt"

func main() {
    ch := make(chan string, 2)
    ch <- "naveen"
    ch <- "paul"
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

这里我们通过语句ch := make(chan string, 2)创建了缓存容量为2的channel。由于容量为2,我们可以向该channel中写入2个数据而不阻塞协程的执行。我们写入了两个字符串,由于没有阻塞,因此后面的打印语句得以执行。该程序输出结果如下:

naveen  
paul

我们再看一个例子来加深下对带缓存channel的理解。

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的channel。开启了write协程后,主协程暂停了2秒。这期间write协程在并发执行,在write协程内我们不断的向channel内写入数据,但是由于channel的容量为2,我们仅能先写进去2个数据,即01,然后该协程进入阻塞状态,直到channel内的数据被读走。因此该程序先输出如下:

successfully wrote 0 to ch  
successfully wrote 1 to ch

然后主协程暂停2秒后开始读取channel内的数据,每读出一个数据主协程又要暂停2秒,这个暂停期间write协程中发现channel内多了一个缓存位置,便继续写入一个数据,然后缓存又变满,再次进入阻塞状态。该程序输出如下:

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

死锁

上一节我们已经介绍过死锁了:

如果某个goroutine向某个channel发送数据,那么需要某个其他goroutine接收该channel的数据,否则会发送运行时错误:deadlock。类似的,如果某个goroutine正在等待从某个channel中接收数据,那么需要某个其他goroutine向该channel中写入数据,否则也会发送运行时错误:deadlock

上一节我们讨论的channel都是不带缓存的,因此前面的说法是成立的。对于带缓存的channel死锁条件则是缓存满了,或者缓存空了。就是说,如果channel缓存已经满了,再继续向channel写入数据时,那么就需要存在某个其他goroutine接收该channel的数据,否则陷入死锁状态;如果channel缓存已经空了,再继续从该channel中读取数据时,需要存在某个其他goroutine向该channel中写入数据,否则陷入死锁状态。

package main

import "fmt"

func main() {
    ch := make(chan string, 2)
    ch <- "naveen"
    ch <- "paul"
    ch <- "steve"
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

这里我们向一个缓存容量为2的channel中写入了3个数据。这时必须有某个goroutine从该channel中读取数据才行,但是并没有,因此陷入死锁状态。该程序执行报如下错误:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
        /Users/goWorkspace/src/test/main.go:9 +0x90
exit status 2

channel长度 vs channel容量

channel容量指的是channel最多能缓存的数据个数,是我们用make初始化channel时的第二个参数。channel长度指的是channel当前时刻缓存的数据个数。

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的channel,因此最多能缓存3个数据。接着我们写进去2个数据,这样缓存内就有2个数据,因此长度为2。然后又读取了1个数据,这时还剩下1个数据,因此长度为1。该程序执行结果如下:

capacity is 3
length is 2
read value naveen
new length is 1

Last updated