【博客412】Go channel

内容:记录golang的实践用法

channel的概念和语法

一个channel是一个先进先出的消息队列。
channel用来在协程[goroutine]之间传递数据,准确的说,是用来传递数据的所有权。
一个设计良好的程序应该确保同一时刻channel里面的数据只会被同一个协程拥有,
这样就可以避免并发带来的数据不安全问题[data races]

channel的类型

像数组、切片和字典一样,channel类型是一种组合类型,每一种channel类型都对应着一种数据类型。
比如元素的类型是string,那么对应的channel类型就是chan string,
进入channel的数据也就必须是string类型的值。


官方的go编译器限制channel里的单个元素最多65535个字节,也就是说如果channel缓冲数组里面
容纳的是struct,那这个struct的size不能大过65535。尽管如此,我们也不应该传递体积过大的
元素值,因为channel的数据从进入到流出会涉及到数据拷贝操作。
如果元素体积过大,最好的方法还是使用传递指针来取代传递值。

channel方向性:

channel类型是可以带有方向的,假设T是一种类型

chan T是双向channel类型,编译器允许对双向channel同时进行发送和接收。

chan<- T是只写channel类型,编译器只允许往channel里面发送数据。

<-chan T是只读channel类型,编辑器只允许从channel里面接收数据。

双向类型的channel,可以被强制转换成只读channel或者是只写channel,
但是反过来却不行,只读和只写channel是不可以转换成双向channel的。

channel零值:

channel类型的零值形式称为空channel。一个非空channel类型必须通过make关键字进行创建。
例如make(chan int, 10)将会创建出一个可以容纳10int值的channel。第二个整形的参数
值代表的就是channel可以容纳数据的大小,如果不提供这个参数值,那默认值就是零。

var ch chan string; // nil channel
ch := make(chan string); // zero channel
ch := make(chan string, 10); // buffered channel

channel阻塞性:

channel里面的value buffer的容量也就是channel的容量。
channel的容量为零表示这是一个阻塞型通道,非零表示缓冲型通道[非阻塞型通道]

channel内部结构:

每个channel内部实现都有三个队列

接收消息的协程队列:
这个队列的结构是一个限定最大长度的链表,所有阻塞在channel的接收操作的协程都被放在这个队列

发送消息的协程队列:
这个队列的结构是一个限定最大长度的链表。所有阻塞在channel的发送操作的协程都被放在这个队列

环形数据缓冲队列:
这个环形数组的大小就是channel的容量。
如果数组装满了,就表示channel满了;
如果数组里一个值也没有,就表示channel是空的。

对于一个阻塞型channel来说,它总是同时处于即满又空的状态。

一个channel被所有使用它的协程所引用,也就是说,只要这两个装了协程的队列长度大于零,
那么这个channel就永远不会被垃圾回收。另外,协程本身如果阻塞在channel的读写操作上,
这个协程也永远不会被垃圾回收,即使这个channel只会被这一个协程所引用。

注意:当channel没有被任何协程使用时,就会被回收

channel的使用:

# 使用cap(ch)函数查询channel的容量,cap是golang的内置函数

# 使用len(ch)函数查询channel内部的数据长度,len函数也是内置的,

# 使用close(ch)关闭channel,close也是内置函数。一个非空channel只能够被关闭一次,
如果关闭一个已经被关闭的或者是关闭一个空channel将会引发panic。



# 使用ch <- v发送一个值v到channel。发送值到channel可能会有多种结果,即可能成功,
也可能阻塞,甚至还会引发panic,取决于当前channel在什么状态。

# 使用 v, ok <- ch 接收一个值。第二个遍历ok是可选的,它表示channel是否已关闭。
接收值只会又两种结果,要么成功要么阻塞,而永远也不会引发panic。

注意:关闭一个只读channel是非法的,编译器直接报错!
注意:所有的这些操作都是同步的协程安全的,不需要加任何其它同步控制!

# For-Range

for-range语法可以用到通道上。循环会一直接收channel里面的数据,直到channel关闭。
不同于array/slice/map上的for-range,channel的for-range只允许有一个变量。

for v = range aChannel {
	// use v
}
等价于

for {
	v, ok = <-aChannel
	if !ok {
		break
	}
	// use v
}
注意,for-range对应的channel不能是只写channel。

# Select-Cases

select块是为channel特殊设计的语法,它和switch语法非常相近。
分支上它们都可以有多个case块和做多一个default块,但是也有很多不同

select 到 括号{之间不得有任何表达式

注意:

Go里面switch默认相当于每个case最后带有break,匹配成功后不会自动向下执行其他case,
而是跳出整个switch, 但是可以使用fallthrough强制执行后面的case代码,fallthrough
不会判断下一条case的expr结果是否为true。

但是fallthrough关键字不能用在select里面!

所有的case语句要么是channel的发送操作,要么就是channel的接收操作

select里面的case语句是随机执行的,而不能是顺序执行的。
设想如果第一个case语句对应的channel是非阻塞的话,case语句的顺序执行会导致后续的case语句
一直得不到执行除非第一个case语句对应的channel里面的值都耗尽了。

如果所有case语句关联的操作都是阻塞的,default分支就会被执行。如果没有default分支,
当前goroutine就会阻塞,当前的goroutine会挂接到所有关联的channel内部的协程队列上。
所以说单个goroutine是可以同时挂接到多个channel上的,甚至可以同时挂接到同一个channel
的发送协程队列和接收协程队列上。当一个阻塞的goroutine拿到了数据接触阻塞的时候,
它会从所有相关的channel队列中移除掉。

channel规则表

## 空channel:

* 关闭一个空channel会导致当前goroutine引发panic

* 向一个空channel发送值会导致当前的goroutine阻塞

* 从一个空channel接收值也会导致当前的goroutine阻塞

* 在空channel上的调用len和cap函数都统一返回零。


# 已关闭的Channel

* 关闭一个已关闭的channel会引发panic

* 向一个已关闭的channel发送值会引发panic。当这种send操作处于select块里面的case语句上时,
  它会随时导致select语句引发panic。

* 从一个已关闭的channel上接收值既不会阻塞也不能panic,它一直能成功返回。
  只是返回的第二个值ok永远是false,表示接收到的v是在channel关闭之后拿到的,
  对应得值也是相应元素类型的零值。可以无限循环从已关闭的channel上接收值。


# 活跃的Channel

* 关闭操作

从channel的接收协程队列中移除所有的goroutine,并唤醒它们。

一个已关闭的channel内部的缓冲数组可能不是空的,没有接收的这些值会导致channel对象
永远不会被垃圾回收。

* 发送操作

如果是阻塞型channel,那就从channel的接收协程队列中移出第一个协程,然后把发送的值直接
递给这个协程。

如果是阻塞型channel,并且channel的接收协程队列是空的,那么当前的协程将会阻塞,
并进入到channel的发送协程队列里。

如果是缓冲型channel,并且缓冲数组里还有空间,那么将发送的值添加到数组最后,当前协程不阻塞。

如果是缓冲型channel,并且缓冲数组已经满了,那么当前的协程将会阻塞,并进入到channel的
发送协程队列中。

* 接收操作

如果是缓冲型channel,并且缓冲数组有值,那么当前的协程不会阻塞,直接从数组中拿出第一个值。
如果发送队列非空,还需要将队列中的第一个goroutine唤醒。

如果是阻塞型channel,并且发送队列非空的话,那么唤醒发送队列第一个协程,该协程会将发送的
值直接递给接收的协程。

如果是缓冲型channel,并且缓冲数组为空,或者是阻塞型channel,并且发送协程队列为空,
那么当前协程将会阻塞,并加入到channel的接收协程队列中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值