Go中的Channel

复习下Golang中的Channel学习使我快乐

译自Channels in Go,该文章实际分两部分,下一部分是Channels in Go - range and select,译文为Go中的Channel——range和select,同样在我的Golang文集中

goroutine允许我们并行的运行一些代码。但是要想让这些代码对我们来说更有意义,我们会有一些额外的需求--我们应该能够传递数据到正在运行的进程中;当并行的进程成功产生数据时,我们应该能从该进程中获取到数据。channel配合goroutine提供了实现这些需求的途径

channel可以想象为一个指定了大小和容量的管道或传送带。我们可以在其一边放置内容,然后在另一边获取内容


图示channel.png

我们采用一个蛋糕制作装箱工厂的例子来进行下面的说明。我们有一台用于制作蛋糕的机器,还有一台用于装箱的机器。她们通过一条传送带互相连接--蛋糕机将蛋糕放上传送带,装箱机在发现传送带上么有蛋糕时将其取走并装入箱子

在go中,chan关键字用于定义一个channel。make关键字用于创建cahnnel,创建时指定channel传递的数据类型

示例代码1
ic := make(chan int) //a channel that can send and receive an int
sc := make(chan string) //a channel hat can send and receive a string
myc := make (chan my_type) //a channel for a custom defined struct type

在channel的变量名前面或后面,你可以使用<-操作符来指示channel用于发送还是接收数据(注意对应关系).假设,my_channel是一个接收int类型数据的channel,你可以像my_channel <- 5这样向其发送数据,并且你可以像my_recvd_value <- my_channel这样来从中接收收据

想象channel是一个有方向的传送带:
从外部指向channel的箭头用于向channel放置数据
从channel指向外部的箭头用于从channel获取数据

示例代码2
my_channel := make(chan int)

//within some goroutine - to put a value on the channel
my_channel <- 5 

//within some other goroutine - to take a value off the channel
var my_recvd_value int
my_recvd_value = <- my_channel

当然,你也可以指定channel中的数据移动方向,只需要在创建channel时在chan关键字旁使用<-指明方向

示例代码3
ic_send_only := make (<-chan int) //a channel that can only send data - arrow going out is sending
ic_recv_only := make (chan<- int) //a channel that can only receive a data - arrow going in is receiving 

channel能够保有的数据个数很重要。她能指示具体有多少条数据可以同时工作。即使发送者有能力产生很多条目,如果接受者没有能力接收她们,那么她们就不能工作。这将会有很多蛋糕从传送带上掉落并浪费掉ORZ。在并行计算中,这叫做生产者-消费者同步问题(producer-consumer synchronization problem)

如果channel的容量(capacity)是1——也就是说,一旦有数据被放入channel,那么该数据必须被取走才能让另一条数据放入,这就是同步channel(synchronous channel)。channel的每一边——发送者和接受者——在同一时间只交流一条数据,然后必须等待,直到另一边完成了相应的发送或接收动作

目前为止,我们定义的所有的channel默认都是同步channel,也就是说,一条数据被放入channel后必须被取走才能再放置另一条数据。现在,我们完成上面提到的蛋糕制作装箱工厂。由于channel在不同的goroutine之间交流数据,我们有两个名为makeCakeAndSendreceiveCakeAndPack的函数。每个函数都接收一个channel的引用作为参数,这样它们可以通过该channel进行交流

示例代码4
package main

import (
    "fmt"
    "time"
    "strconv"
)

var i int

func makeCakeAndSend(cs chan string) {
    i = i + 1
    cakeName := "Strawberry Cake " + strconv.Itoa(i)
    fmt.Println("Making a cake and sending ...", cakeName)
    cs <- cakeName //send a strawberry cake
}

func receiveCakeAndPack(cs chan string) {
    s := <-cs //get whatever cake is on the channel
    fmt.Println("Packing received cake: ", s)
}

func main() {
    cs := make(chan string)
    for i := 0; i<3; i++ {
        go makeCakeAndSend(cs)
        go receiveCakeAndPack(cs)

        //sleep for a while so that the program doesn’t exit immediately and output is clear for illustration
        time.Sleep(1 * 1e9)
    }
}
输出结果
Making a cake and sending ... Strawberry Cake 1 
Packing received cake: Strawberry Cake 1 
Making a cake and sending ... Strawberry Cake 2 
Packing received cake: Strawberry Cake 2 
Making a cake and sending ... Strawberry Cake 3 
Packing received cake: Strawberry Cake 3

在上述代码中,我们创建了三个制作蛋糕的函数调用,并在其之后立刻创建了三个装箱蛋糕的函数调用。我们知道,每当一个蛋糕被装箱,就会有另一个蛋糕同时被制作并准备好被装箱。当然如果你吹毛求疵,代码中确实有一个很轻微的含混之处——在打印Making a cake and sending …和实际发送蛋糕到channel之间有延时。代码中我们在每个循环中调用了time.Sleep(),用于让制作和装箱动作一个接一个的发生,这样做是正确的。由于我们的channel是同步的,而且同一时间仅支持一条数据,一个从channel中移除蛋糕的动作也就是一个装箱动作,必须在制作新蛋糕并将其放入channel之前发生

现在我们改动下上面的内容让其更像我们正常使用的代码。典型的goroutine一般是一个包含了不断循环的内容的代码块,其内部完成一些操作并且与其他的goroutine通过channel交换数据。在下面的例子中,我们将循环移至goroutine函数内部,然后我们仅调用该goroutine一次

示例代码5
package main                                                                                                                                                           

import (
    "fmt"
    "time"
    "strconv"
)

func makeCakeAndSend(cs chan string) {
    for i := 1; i<=3; i++ {
        cakeName := "Strawberry Cake " + strconv.Itoa(i)
        fmt.Println("Making a cake and sending ...", cakeName)
        cs <- cakeName //send a strawberry cake
    }   
}

func receiveCakeAndPack(cs chan string) {
    for i := 1; i<=3; i++ {
        s := <-cs //get whatever cake is on the channel
        fmt.Println("Packing received cake: ", s)
    }   
}

func main() {
    cs := make(chan string)
    go makeCakeAndSend(cs)
    go receiveCakeAndPack(cs)

    //sleep for a while so that the program doesn’t exit immediately
    time.Sleep(4 * 1e9)
}
输出结果
Making a cake and sending ... Strawberry Cake 1 
Making a cake and sending ... Strawberry Cake 2 
Packing received cake: Strawberry Cake 1 
Packing received cake: Strawberry Cake 2 
Making a cake and sending ... Strawberry Cake 3 
Packing received cake: Strawberry Cake 3

输出结果在我电脑上是这样的,在你电脑上可能会不同,输出结果依赖于你机器上goroutine的执行顺序。如前所述,我们仅调用了每个goroutine一次,并且传递了一个公有的channel给她们。在每个goroutine内部有三个循环,makeCakeAndSend将蛋糕放入channel,receiveCakeAndPack将蛋糕从channel中取出。由于程序会在我们创建了两个goroutine后立即结束,因此我们必须手动增加一个时间暂停操作来让三个蛋糕都被制作和装箱好

极其重要的一点是,我们必须理解,上面的输出并没有正确的反应channel中实际的发送和接收操作。发送和接收在这里是同步的——同一时间仅有一个蛋糕。然而由于在打印语句和实际发送与接收间的延时,输出看起来在顺序上是错误的。而实际上发生的是:

Making a cake and sending ... Strawberry Cake 1 
Packing received cake: Strawberry Cake 1 
Making a cake and sending ... Strawberry Cake 2 
Packing received cake: Strawberry Cake 2 
Making a cake and sending ... Strawberry Cake 3 Packing received cake: Strawberry Cake 3

因此,一定要记住,在处理goroutine和channel时,通过打印日志分析执行顺序一定要万分小心

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容

  • 译自Channels in Go - range and select,该文章分为两部分,第一部分的翻译见Go中的...
    Kenshinsyrup阅读 39,859评论 3 15
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,112评论 18 139
  • Go的内存模型 看完这篇文章你会明白 一个Go程序在启动时的执行顺序 并发的执行顺序 并发环境下如何保证数据的同步...
    初级赛亚人阅读 2,809评论 0 2
  • Goroutine是Go里的一种轻量级线程——协程。相对线程,协程的优势就在于它非常轻量级,进行上下文切换的代价非...
    witchiman阅读 4,744评论 0 9
  • 本文翻译自Sameer Ajmani的文章《Go Concurrency Patterns: Pipelines ...
    大蟒传奇阅读 3,751评论 0 15