Golang Channel详解
in Go with 0 comment

Golang Channel详解

in Go with 0 comment

Golang Channel详解

channel的本质是一个并发安全的FIFO队列

Go中的channel是实现CSP并发模型的关键基础

CSP是 是 Communicating Sequential Processes(通信顺序进程)的简称。在 CSP 中,多了一个角色 Channel,Worker 之间不直接通信,而是通过 Channle 进行通信。

Channel 是过程的中间媒介,Worker1 想要跟 Worker2 发信息时,直接把信息放到 Channel 里(在程序中其实就是一块内存),然后 Worker2 在方便的时候到 Channel 里获取。

Channel 类似 Unix 中的 Pipe

https://jpanj.com/2020/actors-and-csp-introduce/

初始化

c := make(chan int, 100)

方向

注:代表任何类型,比如intfloat、自定义类型等

​ 定义时,也可以定义为单向chan,但无实际使用意义

chan <T>     // 默认为双向,可以接收和发送类型为 T 的数据,定义或函数入参时使用
chan<- <T>   // 单向,只可以用来发送 <T> 类型的数据,在函数参数中使用, 这样可以限定chan使用
<-chan <T>   // 单向,只可以用来接收 <T> 类型的数据,在函数参数中使用, 这样可以限定chan使用

<-总是优先和最左边的类型结合。(The <- operator associates with the leftmost chan possible)

chan<- chan int    // 等价 chan<- (chan int)
chan<- <-chan int  // 等价 chan<- (<-chan int)
<-chan <-chan int  // 等价 <-chan (<-chan int)
chan (<-chan int)

同步异步

无缓存

同步channel,发送者和接受者都会阻塞等待数据

c := make(chan int, 100)
c := make(chan int)

有缓存

对于发送者,当缓存未满时为异步不阻塞channel,缓存满时为同步channel

对于接收者,当缓存不为空时为异步不阻塞channel,为空时时为同步channel

其他操作

https://colobu.com/2016/04/14/Golang-Channels/

for …… range

func main() {
	go func() {
		time.Sleep(1 * time.Hour)
	}()
	c := make(chan int)
	go func() {
		for i := 0; i < 10; i = i + 1 {
			c <- i
		}
		close(c)
	}()
	for i := range c {	//如果c的缓冲区为空,这个协程会一直阻塞,除非被channel被close
		fmt.Println(i)
	}
	fmt.Println("Finished")
}

range c产生的迭代值为Channel中发送的值,它会一直迭代直到channel被关闭。上面的例子中如果把close(c)注释掉,程序会一直阻塞在for …… range那一行

带缓冲的channel,被关闭后也可以读出缓冲区的值,关闭后再对channel进行操作,行为如上面的表格所述。

select

select语句选择一组可能的send操作和receive操作去处理。它类似switch,但是每条case只是用来处理channel操作。
它的case可以是send语句,也可以是receive语句,亦或者default

receive语句可以将值赋值给一个或者两个变量。

最多允许有一个default case,它可以放在case列表的任何位置,尽管我们大部分会将它放在最后

如果有同时多个case去处理,比如同时有多个channel可以接收数据,那么Go会伪随机的选择一个case处理(pseudo-random)。如果没有case需要处理,则会选择default去处理,如果default case存在的情况下。如果没有default case,则select语句会阻塞,直到某个case需要处理。

func chanFunc(c, quit chan int) {
	for {
		select {
		case r, ok := <-c:	// 建议使用双返回值,来确定channel是否被关闭
			if ok {
				fmt.Println("Goroutine Recv", r)
			} else {
				fmt.Println("Chan Closed")
			}
		case <-quit:
			fmt.Println("Quit")
			return
		}
	}
}
func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		fmt.Println("main Send", 2)
		c <- 2
		time.Sleep(time.Second)
		close(c)
		fmt.Println("Close c")
		quit <- 0
	}()
	chanFunc(c, quit)
}


// main Send 2
// Goroutine Recv 2
// Close c
// Quit

Timer结合,实现timeout

select有很重要的一个应用就是超时处理。 因为上面我们提到,如果没有case需要处理,select语句就会一直阻塞着。这时候我们可能就需要一个超时操作,用来处理超时的情况。

func main() {
	c1 := make(chan string, 1)
	go func() {
		time.Sleep(time.Second * 2)
		c1 <- "result 1"
	}()
	select {
	case res := <-c1:
		fmt.Println(res)
	case <-time.After(time.Second * 1):
		fmt.Println("timeout 1")
	}
}
// timeout 1

该demo的功能是,res预期在1s钟时间内收到一条channel消息,否则超时

其他

channel是值传递?

行为总结

操作nil channelclosed channelnot-closed non-nil channel
closepanicpanic成功 close
ch <-一直阻塞panic阻塞或成功写入数据
<- ch一直阻塞读取对应类型零值阻塞或成功读取数据

实现详解

https://changkun.de/golang/zh-cn/part2runtime/ch09lang/chan/

channel的阻塞与唤醒 gopark ready

参考资料

https://www.jianshu.com/p/554e210bdca4

http://litang.me/post/golang-channel/

http://legendtkl.com/2017/07/30/understanding-golang-channel/

https://colobu.com/2016/04/14/Golang-Channels/#Buffered_Channels