golang语言异步通信之Channel

golang语言异步通信之Channel

简介

Channel主要用在go routine之间作为异步通信工具。
简单说我们可以把Channel理解为一个队列,有人负责往里面写,有人负责从里面读,Channel会保证读和写操作的时序性和原子性,先写入的数据一定先读出来,而且一次读写都是一个完整的数据类型。

基本用法

ch <- val    // 把值val写入Channel ch
val := <-ch  // 从Channel ch中读取值,并且写入变量val

操作符号是<-,箭头向指明数据的流向,注意没有相对的->操作符,

创建Channel

ch := make(chan int)
或者
var ch chan int
ch = make(chan int, 1)

和map、slice一样,chan必须先make出来然后才能使用。

注意make的第二个参数,用来表明chan的缓冲大小,表明允许写入的数据的个数,在缓冲没有满之前,写操作直接返回,而一旦缓冲满了,则写操作会被阻塞,只到有新的缓冲空间释放出来。
而如果大小为0,则表示没有缓冲,则任何写入操作都会阻塞,知道有读请求进来。

关闭 Channel

close(ch)

一个Channel一旦被关闭了,就不再允许继续往里面写入数据,否则会引发panic错误

panic: send on closed channel

注意,panic只针对写操作,而对于读操作不会panic,而是会立即返回:

  1. 如果Channel里面还有数据,则正常返回读取的数据,就像Channel没有关闭一样。
  2. 如果Channel里面已经没有数据,则返回对应数据类型的零值。如果是int则返回0,如果是string则返回""

另外可以使用一个额外的参数来检查channel是否已经被关闭了(以示区别读取的是一个真的零值,还是channel已经关闭返回的零值)。

v, ok := <- ch

如果ok 是false,表明这个channely已经被关闭了。

Range处理Channel

for i := range ch {
    fmt.Println(i)
}

这个for循环会一直迭代,直到channel被关闭;如果ch里面已经没有数据了,for循环会阻塞,只到新的数据进来。

select操作

select语句语法类似switch,但只能用来处理Channel相关的操作。
它的分支(case)可以是Channel的读语句,也可以是Channel的写语句,或者是default语句。

  1. 对于读case语句,如果当前Channel有数据可读,则执行。
  2. 对于写case语句,如果当前Channel有缓冲可写,则执行。
  3. 如果既没有可读,又没有可写,则执行default语句,如果没有default语句,则阻塞select语句。
  • 例子 1
    这个例子走写的分支,即会打印"put value to chan",因为ch没有数据第一个case读操作不满足,第二个case写可以写入,因为Channel有一个缓冲大小。
func main() {
    ch := make(chan int, 1)
    select {
        case i := <- ch:
            fmt.Printf("get value from chan: %d\n", i)
        case ch <- 1:
            fmt.Printf("put value to chan\n")
    }
}
  • 例子 2
    这个例子会阻塞,因为没有数据读,而又没有缓冲,写也不能成功。
func main() {
    ch := make(chan int)
    select {
        case i := <- ch:
            fmt.Printf("get value from chan: %d\n", i)
        case ch <- 1:
            fmt.Printf("put value to chan\n")
    }
}
  • 例子 3:
    这个例子会走到default分支,打印出"select default branch",因为读和写的分支都不满足,又有default分支。
func main() {
    ch := make(chan int)
    select {
        case i := <- ch:
            fmt.Printf("get value from chan: %d\n", i)
        case ch <- 12:
            fmt.Printf("put value to chan\n")
        default:
            fmt.Printf("select default branch\n")
    }
}

select的Timeout

前面我们看到如果select语句没有分支(case)语句能够处理,而又当没有定义default分支时,select会一直阻塞,这时候就需要一个超时机制。

func main() {
    ch := make(chan int, 1)
    select {
        case i := <- ch:
            fmt.Printf("get value from chan: %d\n", i)
        case <-time.After(time.Second * 2):
            fmt.Println("timeout 2 seconds")
    }
}

这个例子里,两秒之后超时会触发,打印"timeout 2 seconds"。
这个超时机制听起来很高级,其实它就是利用的是time.After方法,我们看下time.After的定义:

# time/sleep.go
func After(d Duration) <-chan Time {
    return NewTimer(d).C
}
...
func NewTimer(d Duration) *Timer {
    c := make(chan Time, 1)
    t := &Timer{
        C: c,
        r: runtimeTimer{
            when: when(d),
            f:    sendTime,
            arg:  c,
        },
    }
    startTimer(&t.r)
    return t
}

我们看到time.After()返回的是一个类型为<-chan Time的单向的channel,这个channel里面有一个值,是一个时间戳值,从而保证在时间到期之后返回的channel里面有一个值,那么这个分支的读取操作能够满足不会阻塞。

想到另外一个场景,select的各个分支都是函数,函数返回一个Channel类型值。

func foo1(ch chan int) chan int {
    fmt.Printf("in foo1\n")
    return ch
}

func foo2(ch chan int) chan int {
    fmt.Printf("in foo2\n")
    return ch
}

func main() {
    ch1 := make(chan int, 1)
    ch2 := make(chan int, 1)

    ch1 <- 11
    ch2 <- 22

    select {
        case i := <- foo1(ch1):
            fmt.Printf("get value from chan1: %d\n", i)
        case i := <- foo2(ch2):
            fmt.Printf("get value from chan2: %d\n", i)
    }
}

试猜想执行输出结果是什么?是下面这个吗?

in foo1
get value from chan1: 11

很多人第一直觉应该是的;我们运行一下看看结果:

$ go build && ./main 
in foo1
in foo2
get value from chan1: 11
$ go build && ./main 
in foo1
in foo2
get value from chan1: 11
$ go build && ./main 
in foo1
in foo2
get value from chan2: 22

上面运行了三次,注意两点:

  1. select的两个分支任何一个都可能被执行到,他们是随机的。即当select的多个分支都满足条件时,哪一个会被选中执行是随机的,不是严格按照代码顺序。
  2. 不管执行了哪一个select分支,函数foo1()和函数foo2()都被执行了。这个怎么理解呢,因为select语句关注的是channel对象,只有执行了函数foo1()和foo2()才能从返回值中拿到Channel对象,从而才能知道Channel是否能够满足读或者写的需求,即只有找到各个select分支的Channel对象本身,才能判断Channel的状态。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • channel[通道]是golang的一种重要特性,正是因为channel的存在才使得golang不同于其它语言。...
    码洞阅读 22,319评论 1 53
  • channel[通道]是golang的一种重要特性,正是因为channel的存在才使得golang不同于其它语言。...
    码洞阅读 2,184评论 0 51
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,295评论 18 399
  • 你好,这是2017年的第三篇文章。 能用、好用、爱用,这三个层次往往被用来形容互联网产品。能用是基础,说明用户可以...
    邹志楠阅读 3,446评论 10 83
  • 序 流芳儿梦游到岳麓山下,便见一颗大玉石,玉石边正躺着一位正酣睡的老者。流芳儿的到来触醒了那老者,经过聊,得知那老...
    庞文钊阅读 446评论 0 0