女主宣言

Go有两个重要的内置功能,同时也是它的特性。分别是channel、Goroutine。这两个特性使Go编写并发程序变的简单、有趣。本文将主要介绍channel。原文来自go101,本文是翻译后留存,方便自己学习。

PS:丰富的一线技术、多元化的表现形式,尽在“HULK一线技术杂谈”,点关注哦!

Channel Introduction


不要通过共享内存来通信,应该通过通信来共享内存,出自Rob Pike。是在Go社区非常流行的一句话,而channel就是为此而生。

通过共享内存来通信与通过同通信来共享内存是两种模式的并发程序。当通过共享内存来通信,我们需要使用一些传统的并发技术,例如互斥锁,来保证共享内存可以被安全的访问,防止数据竞争。

channel是在多个Goroutine之间传递数据和同步的重要手段,而对通道的操作本身也是同步的。同一时刻,仅有一个Goroutine能向一个channel发送元素值,同时仅有一个Goroutine能从它那里接受元素值。在channel中,各个元素值都是严格按照发送到此的先后顺序排序,最早被发送至channel的元素值会最先被接收。它类似一个内部的FIFO(first in,first out先进先出)数据队列。

此外,channel中的元素值都具有原子性,是不可被分割的。channel中的每个元素都只能被某一个Goroutine接收,已被接收的元素值会立刻从channel中删除。

某些value的使用权会随着value在Goroutines间传递,当Goroutine发送数据到channel会释放value的所有权,而在接收数据时会同时获得value的所有权。

Go也支持一些传统的并发技术,但channel应该优先被考虑。

老实说,每个并发同步技术都有其最佳的使用场景。 但channel应用范围更广,使用场景更多。 而且在许多情况下,使用channel的并发代码通常比使用其他数据同步处理技术看起来更清晰和易于理解。

1

Channel 的分类

channel是复合类型,类似array、slice、map,每个channel都有一个元素类型。 所有要发送到通道的数据都必须是元素类型的值。channel可以为nil

channel可分为双向、单向,假设以下的T是任意类型:

  • chan T, 双向channel,同时允许发送数据到channel、从channel接收数据。

  • chan<- T,单向channel,只允许发送数据到channel,操作符<-形象的表示了元素值的流向。

  • <-chan T,单向channel,只允许从channel接收数据,这次操作符<-位于关键字chan左边,这真的很棒。

使用内置的make函数可以创建一个channel,下面这个例子会创建一个元素类型为int的channel,make函数的第二个参数为可选参数,可以设置channel的容量,默认为0。

2

Channel 的操作

这里有5个channel的操作,假设ch为channel类型

  1. 关闭channel

close是一个内置函数,参数必须是channel类型变量且不能是<-ch类型。

2. 发送值到channel

3. 从channel接收值

接收操作至少返回一个元素类型的值,它也可以用作赋值表达式

4. 查看channel容量

cap是一个内置函数,返回的值为int类型

5. 查看channel中当前值的数量

len是一个内置函数,返回的值为int类型。

如果channel元素类型为nil,cap、len将返回0。

3

Channel 操作规则总结

为了能将channel解释清楚,剩余的文章中会将channel分为三类:

  1. nil channel

  2. non-nil但已关闭的channel

  3. not-nil未关闭的channel

下面这个表简单表述以上三类channel的操作场景

背景简介

表中5种未标记的场景,应用规则非常清晰:

  • 关闭nil channel或已关闭的channel会引发panic

  • 发送元素值至已关闭的channel会引发panic

  • 发送元素值至nil channel或从nil channel接收元素值,都会导致当前goroutine永久阻塞

未标记的4种场景在下面会详细解释

为了更好的理解channel,先了解下channel的内部结构。我们可以认为每个channel在内部维护3个队列

  1. receiving goroutine queue(简称RGQ)是没有大小限制的链表,队列中是阻塞的receiving goroutine,准备存储值的地址也与每个goroutine一起存储在队列中

  2. sending goroutine queue(简称SGQ)同样是没有大小限制的链表,队列中是阻塞的sending goroutine, 准备发送的值的地址也与每个goroutine一起存储在队列中

  3. value buffer queue(简称VBQ)是个圆形队列,大小等于channel的容量。如果队列中值的数量达到channel的容量,channel会以full状态被调用。如果队列中没有存储值,channel会以emply状态被调用。容量为0的channel只能是full或emply。

Channel 规则场景 A

当gorontine尝试从非nil未关闭channel接收元素值,该goroutine先尝试获得channel关联的锁,然后执行以下步骤,直到满足一个条件。

  1. 如果channel的VBQ不为空,这种情况下channel的RGQ必须为空,此时的goroutine将通过unshift从VBQ接收元素值。 如果channel的SGQ也不为空,则将SGQ中的某个sending goroutine通过unshift移出队列并设置为运行状态,要发送的元素值将被放入channel的VBQ,receiving goroutine继续运行。该场景下channel的发送操作是非阻塞的

  2. 如果channel的VBQ为空,但SGQ不为空。这种情况下channel必须为非缓冲channel。receiving goroutine将从RGQ中unshift出某个sending goroutine,并接收这个sending goroutine发送的元素值。该sending goroutine将被解锁并设置为运行状态。该场景下channel的发送操作是非阻塞的

  3. 如果channel的VBQ和SGQ都为空,此时的gorontine会被放入RGQ中,进入(停留)在阻塞状态。当有其他goroutine发送元素值到channel,它可能恢复运行。该场景下channel的发送操作是阻塞的

Channel 规则场景 B

当goroutine尝试发送元素值至非空未关闭的channel,该goroutine先尝试获取channel关联的锁,然后执行以下步骤,直到满足一个条件。

  1. 如果channel的RGQ不为空,这种情况下VBG必须为空,sending goroutine将从RGQunshift出某个receiving goroutine,并发送元素值至这个receiving goroutine。sending goroutine继续运行。该场景下channel的发送是非阻塞的。

  2. 如果channel的RGQ为空,并且VBQ没有满。在这种情况下SGQ必须为空。将sending goroutine要发送的元素值放入VBQ,sending goroutine继续运行。该场景下channel的发送操作是非阻塞的。

  3. 如果channel的RGQ为空,并且VBQ已满。sending goroutine将被放入SGQ,进入(停留)在阻塞状态。当有其他goroutine从channel接收元素值,它可能恢复运行。该场景下channel的发送操作是阻塞的。

Channel 规则场景 C

当goroutine尝试关闭一个非空未关闭的channel,将按照以下顺序执行两个步骤

  1. 如果channel的RGQ不为空,在这种情况下VBQ必须为空。RGQ中所有goroutine会被逐个unshift,并且每个goroutine会接收到一个元素值类型的零值。

  2. 如果channel的SGQ不为空,SGQ中所有gorontine会被逐个unshift,每个向已关闭channel发送元素值的goroutine都会产生一个panic。已经放入到VBG的元素值仍然存在。

Channel 规则场景 D

channel关闭后,channel的接收操作将不会再被阻塞。VBQ已有的元素值可以继续被接收。当VBQ中所有的元素值都被取出后,后续的接收操作都会收到元素值的零值。

通过上述的规则,我们可以得到一些事实

  • 如果channel是关闭的,它的VGQ、SGQ必须为空,但VBQ可以不为空

  • 任何情况下,如果VBQ不为空,那么它的RGQ必须为空

  • 任何情况下,如果VBQ没满,那么它的SGQ必须为空

  • 缓冲channel在任何情况下,SBQ、RGQ其中之一必须为空

  • 非缓冲channel在任何情况下,通常SBQ、RGQ其中之一必须为空,但有一个例外,那就是select可能会导致某个goroutine被放入到这两个队列中。

4

Channel 使用实例

现在来看些channel使用的例子

package main

import "fmt"

func main() {

c := make(chan int) // 非缓冲通道

go func() {

x := <- c // 这里会被阻塞,直到通道收到元素值

c <- x*x  // 这里会被阻塞,直到通道中的值被接收

}()

c <- 3   // 这里会被阻塞,直到通道中的值被接收

y := <-c // 这里会被阻塞,直到通道收到元素值        fmt.Println(y) // 9

}

下面这个例子使用了缓冲通道,但这个程序不是并发的

package main

import "fmt"

func main() {

c := make(chan int, 2) // 缓冲通道

c <- 3

c <- 5

close(c)

fmt.Println(len(c), cap(c)) // 2 2

x, ok := <-c

fmt.Println(x, ok) // 3 true

fmt.Println(len(c), cap(c)) // 1 2

x, ok = <-c

fmt.Println(x, ok) // 5 true

fmt.Println(len(c), cap(c)) // 0 2

x, ok = <-c

fmt.Println(x, ok) // 0 false

x, ok = <-c

fmt.Println(x, ok) // 0 false

fmt.Println(len(c), cap(c)) // 0 2

close(c) // panic!

c <- 7   // also panic if the last line is removed.

}

一场永不停止的足球赛

package main

import (

"fmt"

"time"

)

func main() {

var ball = make(chan string)

kickBall := func(playerName string) {

for {

fmt.Println(<-ball, "kicked the ball.")                        time.Sleep(time.Second)

ball <- playerName

}

}

go kickBall("John")

go kickBall("Alice")

go kickBall("Bob")

go kickBall("Emily")

ball <- "referee" // kick off

var c chan bool   // nil

<-c               // blocking here for ever

}

5

Channel 元素值通过拷贝传递

无论发送元素值至channel还是从channel接收元素值,都会对这个值进行拷贝。类似赋值、函数传参操作。

标准的go编译器,要求channel元素类型不能超过65535。通常,我们不会限制channel的大小,所有通过channel传送的元素值都会发生拷贝。当某个元素值从一个goroutine被传递到另外一个goroutine,会有2个元素值被拷贝。所以如果传送的值很大,最好还是用指针来代替。

6

Channel 中的 For-Range 循环

for-range代码结构也可以应用到channel。循环将尝试迭代的接收channel中的,直到channel被关闭并且VBQ为空。

for v = range aChannel {

// use v

}

等同于

for {

v, ok = <-aChannel

if !ok {

break

}

// use v

}

这里的aChannel不能是只允许发送的通道类型。如果它是一个nil channel, 循环将永久阻塞。

HULK一线技术杂谈

由360云平台团队打造的技术分享公众号,内容涉及云计算、数据库、大数据、监控、泛前端、自动化测试等众多技术领域,通过夯实的技术积累和丰富的一线实战经验,为你带来最有料的技术分享

Channels In Go相关推荐

  1. go channel 缓冲区最大限制_GO语言圣经学习笔记(八)Goroutines和Channels

    奋斗鸭!Day97 知识点 goroutinue 基本用法 golang非常深度的简化了goroutinue的使用方法,异常简单,门槛降低很多 // goroutinue 使用非常简单go f() G ...

  2. miniconda安装,及channels配置,安装其他软件

    在使用linux中,经常会遇到安装软件,配置环境的问题,有一种偷懒的方式,就是使用第三库进行安装,就是类似于在windows下使用软件管家安装软件,这样做最大的便利在于不需要去配置很多的依赖环境 1. ...

  3. OpenCV中的cvCreateImage的参数channels()的意义

    实际上就是MATLAB中的几维矩阵的意义!比如一维矩阵,二维矩阵,三维矩阵~ 看opencv2refman.pdf 官方文档的解释吧! channels – Number of channels pe ...

  4. iOS 提交应用过程出现的错误及#解决方案#images can't contain alpha channels or transparencies...

    本文永久地址为http://www.cnblogs.com/ChenYilong/p/3977542.html ,转载请注明出处. 当你试图通过<预览>"导出"时 (比 ...

  5. WIN10安装scrapy/channels等不成功的解决方式

    问题 在Win10机器上,不管是安装scrapy还是channels,都需要安装一个包,叫做twisted.正是这个twisted,导致出现一系列的奇葩错误,让我一度以为我的Pycharm坏了,还改了 ...

  6. channels java_Java NIO channels

    Java NIO channels Java 中的channel和流有如下的区别 既可以从通道中读取数据,又可以写数据到通道.但流的读写通常是单向的. 通道可以异步地读写. 通道中的数据总是要先读到一 ...

  7. 成功解决PackagesNotFoundError: The following packages are not available from current channels: tensorflo

    成功解决PackagesNotFoundError: The following packages are not available from current channels: tensorflo ...

  8. 【译】Understanding Universal Composition Framework and Sprites State Channels

    围绕EOS进行了很多讨论,以及为扩展性交易一些分权是否合理. 我想我想更好地理解第2层可扩展性解决方案(例如Lightning Network,Raiden). 特别是,我正在阅读Sprites和St ...

  9. [blog摘要]Exploring and Decoding ETW Providers using Event Log Channels

    这是一篇摘要,原文在这里 Exploring and Decoding ETW Providers using Event Log Channels (http://blogs.msdn.com/nt ...

  10. android o preview 3,Android O Preview 之 通知渠道(Notification Channels)

    介绍 Android O 引入了 通知渠道(Notification Channels),以提供统一的系统来帮助用户管理通知,如果是针对 android O 为目标平台时,必须实现一个或者多个通知渠道 ...

最新文章

  1. [自动化]Puppet服务安装和部署
  2. python输入两个数用逗号隔开如不是两个数报错_Python数据类型
  3. MySQL笔记7:sum和count用法总结
  4. PHP函数篇之掌握ord()与chr()函数应用
  5. python求列表最大值下标_切片,丝滑的字符串 | Python基础连载(三)
  6. php面向对象项目,PHP的面向对象编程:开发大型PHP项目的方法(一)
  7. 《Hadoop权威指南》第三章 Hadoop分布式文件系统
  8. java开发展望怎么写_Java开发趋势:2019年展望
  9. “普通人,不要随便创业,安心拿工资过日子比啥都强”你怎么看?
  10. python安装和更新pip
  11. tensorflow基础:tf.data.Dataset.from_tensor_slices()
  12. 3dmax:3dmax动画栏(加载动画【IK计算器、约束、变换控制器、参数编辑器、关联参数、骨骼工具】、关键帧动画【修改关键帧、运动面板、曲线编辑器】、控制器动画【控制器指定】)之详细攻略
  13. 虚幻4学习笔记(1)基本操作和插件导入
  14. Layer 开启与关闭加载层
  15. [编译原理读书笔记][第4章 语法分析]
  16. 找工作神器,提取各大网站有效的招聘信息(前程无忧、智联招聘、猎聘网)
  17. nice,​使用python生成专属二维码~
  18. click是哪个键 wheel_Click是什么意思?键盘上的Click键在哪里?
  19. 服务器网络群搭建(以华为云为例)
  20. autojs 悬浮框演示代码

热门文章

  1. 使用消息中间件时,如何保证消息不丢失且仅仅被消费一次
  2. 诗与远方:无题(三)
  3. idea部署项目com.intellij.javaee.oss.admin.jmx.JmxAdminException-未使用最新版本的war包
  4. 二叉树最简单的遍历方式——二叉树的层序遍历
  5. el alert 点击添加时提示_JavaScript 有三种类型:警告框、确认框和提示框使用详解...
  6. spring boot几个初始配置文件
  7. 决策树算法原理(下)
  8. 开源软件、开源硬件、……开源餐馆来了
  9. 【转】shell十三问,为linux学习打基础(上)
  10. 生产环境使用elasticsearch遇到的一些问题以及解决方法(不断更新)