Go语言第十一课 并发(三)Channel缓存与阻塞

Channel的缓存

前面介绍过channel的创建方法:

channel_test := make(chan string)

其实它完整的写法应该是:

channel_test := make(chan string,0)

这种容量为0的channel就是无缓存channel。对应地,我们也可以创建有缓存channel

channel_test := make(chan string,1)

无缓存channel

channel是否有缓存最根本的不同是阻塞策略的不同。

无缓存的channel会在读写两端产生阻塞即:

当对一个无缓存的channel读时,若数据尚未到达,则读协程阻塞;当对一个无缓存的channel写时,若数据尚未消费,则写协程阻塞。

基于此,无缓存的channel也成为同步channel

需要特别注意的是,当数据尚未被消费时,接收者接收数据发生在唤醒发送者协程之前!

有缓存channel

带缓存的channel实际上是一个阻塞队列。对队列的写总发生在队尾,队列满时写协程阻塞;对队列的读总发生在队尾,队列空时读协程阻塞。

内置函数cap(channel)可以读取队列的总长度。

内置队列len(channel)可以读取队列的有效长度(已使用长度)。

有缓存的channel的最大意义在于生产和消费可以解耦。

协程泄漏

协程永远阻塞,无法退出。即协程泄漏。协程的泄漏无法被自动回收,所以要极力避免!要确保每个协程都可以正常退出。

并发退出

用close(ch)可以实现广播,关注ch的多个协程可以同时听到这个消息从而退出。

package main

import (
	"bufio"
	"os"
	"strings"
	"fmt"
)

func main() {
	channel_test := make(chan string)

	go test(channel_test, 1)
	go test(channel_test, 2)
	go test(channel_test, 3)
	go test(channel_test, 4)
	go test(channel_test, 5)
	go test(channel_test, 6)
	go test(channel_test, 7)
	go test(channel_test, 8)

	for {
		inputReader := bufio.NewReader(os.Stdin)
		str, err := inputReader.ReadString('\n')
		if err == nil {
			if strings.Compare(str, "stop\n") == 0 {
				close(channel_test)
			} else {
				channel_test <- str
			}
		}
	}

}

func test(ch <-chan string, index int) {
	fmt.Printf("start test %d\n", index)
	for {
		msg := <-ch
		if msg == "" {
			break
		}
		fmt.Printf("%d "+msg, index)
	}
	fmt.Printf("exit test %d\n", index)
}
GOROOT=/home/yong/Desktop/go #gosetup
GOPATH=/home/yong/Desktop/go-path #gosetup
/home/yong/Desktop/go/bin/go build -i -o /tmp/___go_build_test_go /home/yong/go/src/channel_test/test.go #gosetup
/tmp/___go_build_test_go #gosetup
start test 3
start test 1
start test 2
start test 4
start test 6
start test 7
start test 5
start test 8
aaa
3 aaa
bbb
1 bbb
ccc
2 ccc
stop
exit test 8
exit test 2
exit test 1
exit test 3
exit test 6
exit test 7
exit test 5
exit test 4


猜你喜欢

转载自blog.csdn.net/yongyu_it/article/details/80886062