注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

being23

写给未来的自己

 
 
 

日志

 
 
关于我

真正的坚定,就是找到力量去做自己喜欢的事情,并为之努力,这样才会觉得生活是幸福的。

网易考拉推荐

Golang Channels  

2017-01-15 19:04:03|  分类: 2017 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

原文见Channels in Golang

Channels是Golang的重要特性,也是Golang独树一帜的特征之一。Channels使得并发编程更容易(至少看起来),灵活以及有趣。

本文给出channel相关的概念,语法和规则。

概念与语法

Channel介绍

Channel可以看作是程序内部的FIFO消息队列。使用Channel的目的是传输值,具体是指,在不同的goroutines之间传递值的所属。良好设计的代码应该保证在任何时刻任何值只属于最多一个goroutine。正确地使用channels,可以避免goroutine之间的数据竞争。

Channel 类型和取值

Channel是组合类型。类似数组,切片和map,channel的具体类型由元素类型决定。所有要发送到channel的数据类型必须与元素类型一致。例如,如果元素类型是string,那么channel的类型就是chan string

Channel是有方向的。假设有类型T,那么chan T就是双向channel类型。对于双向channel,编译器允许从中收发数据。chan<- T是单向发送channel类型。编译器不允许从中接收数据。<-chan T是单向接收channel类型。编译器不允许向其发送数据。

双向channel类型chan T的值可以赋给单向发送类型chan<- T,也可以赋给单向接收类型<-chan T

Channel类型的零值由nil表示。非零channel必须由内置函数make创建。例如,make(chan int, 10)将会创建元素类型是int的channel。函数make的第二个参数表示新建channel的缓冲区大小(见下节)。该参数可选,默认值0。

Channel缓冲区大小经常称为channel的容量。零容量的channel称为阻塞channel,非零容量的channel称为缓冲channel。

Channel内部结构

每个channel维护3个队列:

  • 接收goroutine队列。该队列是一个大小无限制的链表。这个队列中的goroutines称为channel的阻塞接收goroutines。
  • 发送goroutine队列。该队列也是一个大小无限制的链表。这个队列中的goroutine称为channel的阻塞发送goroutines。
  • 值缓冲队列。这是一个循环队列。队列大小在channel创建时指定。如果当前队列中值的数目达到了容量上限,那么channel处于满状态。如果当前队列中没有值,那么channel处于空状态。对于零容量channel,既处于满状态,也处于空状态。

如果发送goroutine队列或者接收goroutine队列不为空,那么channel不会回收。

Channel操作

Channel(假设是ch)可以执行下面的操作:

  • 调用内置函数cap(ch)获取值缓冲队列容量。
  • 调用内置函数len(ch)获取值缓冲队列大小。这个函数中看不中用。
  • 调用内置函数close(ch)关闭channel。非零channel只能关闭一次。在运行时关闭已经关闭的或者nilchannel出现panic。请注意,在Golang中关闭单向接收channel是非法的,无法编译。
  • 通过这种形式ch <- v发送值v。取决于channel的状态,发送操作的结果可能是成功发送值到channel,阻塞发送goroutine,或者发送goroutine出现panic(见下文channel规则)。
  • 通过这种形式v, ok = <- ch接收(获取)值,其中第二个值ok是可选的,用于表明值v是否是channel关闭之前发送的。取决于channel的状态,接收结果可能是成功从channel中接收值,或者阻塞接收goroutine。接收操作永远不会导致接收goroutine出现panic(细节见下文channel规则)。

所有这些操作都是同步的,无需额外的同步措施。然而,跟Golang中的多数操作一样,channel赋值不是同步的。

For-Range作用于Channels

语法for range可用于channels。该循环会尝试迭代获取发送到channel的所有值,直到channel关闭或者内部值缓冲队列为空。与作用于数组、切片和map上for range语法不同的是,作用于channels上时,每次迭代最多返回一个值。

for v = range aChannel {
// use v
}

等价于

for {
v, ok = <-aChannel
if !ok {
break
}
// use v
}

Select-Cases作用于Channels

Golang中的select块语法是特别为channels设计的。该语法非常像switch块语法,例如,在select块中,可以有多个case分支以及至多一个default分支。不过它们之间也有一些明显的区别:

  • select关键字后面({前面)不允许有语句以及表达式。
  • case分支中不允许使用fallthrough语句。
  • select块中的关键字case后面的每一个语句必须是接收操作或者发送操作。
  • select块中关键字case后面的所有的channel操作阻塞当前goroutine时,如果存在default分支,那么执行default分支,否则阻塞当前goroutine,并将其放到关键字case后面操作channel的相应发送goroutine队列或者接收goroutine队列。 
    所以,一个goroutine可以同时位于多个channel的发送goroutine队列和接收goroutine队列。甚至是同一个channel的发送goroutine队列和接收goroutine队列。 
    当goroutine从select块阻塞变成非阻塞时,就会从关键字case后面的所有参与操作channel的发送goroutine队列和接收goroutine队列中移除。

Channel规则完整列表

nilChannel规则

  • close nilchannel,当前goroutine panic。
  • send 值到nilchannel,永久阻塞当前goroutine。
  • nilchannel receive,永久阻塞当前goroutine。

nil channel上调用内置函数lencap总是返回0。)

已关闭的Channel规则

  • close 已关闭的channel,当前goroutine panic。
  • send 值到已关闭的channel,当前goroutine panic。请注意,发送值到已关闭channel是非阻塞操作。
  • 从已关闭channel receive,当前goroutine不会panic也不会阻塞,总是可以成功接收到值。可选的第二个bool类型的返回值表明接收到的值是否在channel关闭之前发送。如果不是channel关闭之前发送的,那么接收值必须是零值。可以从任何关闭的channel获取人任意多的零值。

nil,非关闭Channel规则

接下来,非nil非关闭的channels称为*活跃*channels。

  • close活跃channel 
    • 首先从channel的接收goroutine队列移除所有的goroutine,让它们不再阻塞。这些goroutine中的每一个都会接收一个元素类型的零值。然后
    • 移除发送goroutine队列中的所有的goroutines,并使得其中每个goroutine panic。 
      已关闭channel的值缓冲队列可能是非空的。
  • send值到活跃channel

    1. 如果接收goroutine队列非空,不再阻塞队列中的第一个goroutine,把值传给该goroutine。 
      这种情形下,值缓冲队列必须为空。 
      当前goroutine不会阻塞。 
      非阻塞的接收goroutine会从接收goroutine队列中移除。 
      如果这个非阻塞的goroutine之前阻塞在select case分支,那么该goroutine将会从case操作相关channel的所有相应接收goroutine队列和发送goroutine队列中移除。
    2. 如果channel的值缓冲队列没有满(not full),将值发送到发送缓冲队列。 
      当前goroutine不阻塞。
    3. 将当前goroutine放到channel的发送goroutine队列,阻塞当前goroutine。在这种情形下,接收goroutine队列必须为空,值缓冲队列必须处于满状态。

    (上述三种场景有序,首先执行满足条件的。)

  • 从活跃channel receive

    1. 如果值缓冲队列非空,从中获取第一个值。 
      不阻塞当前goroutine。 
      如果发送goroutine队列非空,不再阻塞队列中的第一个goroutine,将其要发送的值会放到值缓冲区。然后从发送goroutine队列移除。 
      如果这个非阻塞的goroutine之前阻塞在select case分支,那么该goroutine将会从case操作相关channel的所有相应接收goroutine队列和发送goroutine队列中移除。
    2. 如果发送goroutine队列不为空(不过值缓冲队列是空的),队列中的第一个goroutine不再阻塞。非阻塞goroutine要发送的值会传给当前接收goroutine。 
      不阻塞当前goroutine。 
      如果这个非阻塞goroutine之前阻塞在select case分支,那么该goroutine将会从case操作相关channel的所有相应接收goroutine队列和发送goroutine队列中移除。
    3. 阻塞当前goroutine,将当前其放到接收goroutine队列。对于这种场景,发送goroutine队列和值缓冲队列必须都为空。

    (上述三种场景有序,首先执行满足条件的。)

关于Channels的一些真相

根据上面列出来的一些规则,channel内部队列的一些真相如下:

  • 任何时候,发送goroutine队列和接收goroutine队列必须有一个是空的。
  • 任何时候,值缓冲队列和接收goroutine队列必须有一个是空的。
  • 任何时候,如果值缓冲队列不满(not full),那么发送goroutine队列必须是空的。
  • 任何时候,如果值缓冲队列非空(not empty),那么接收goroutine队列必须是空的。
  • 如果channel是关闭的,发送goroutine队列和接收goroutine队列必须为空,但是值缓冲队列可以不为空。
  评论这张
 
阅读(62)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017