Go的channel常见使用方式

go关键字可以用来开启一个goroutine(协程))进行任务处理,而多个任务之间如果需要通信,就需要用到channel了。

func testSimple(){
    intChan := make(chan int)

    go func() {
        intChan <- 1
    }()

    value := <- intChan
    fmt.Println("value : ", value)
}

上面这个简单的例子就是新开启的goroutine向intChan发送了一个1的值,那么在主线程的intChan就会收到这个值的信息。

channel类型:无缓冲和缓冲类型

channel有两种形式的,一种是无缓冲的,一个线程向这个channel发送了消息后,会阻塞当前的这个线程,知道其他线程去接收这个channel的消息。无缓冲的形式如下:

intChan := make(chan int)

带缓冲的channel,是可以指定缓冲的消息数量,当消息数量小于指定值时,不会出现阻塞,超过之后才会阻塞,需要等待其他线程去接收channel处理,带缓冲的形式如下:

//3为缓冲数量
intChan := make(chan int, 3)

传输struct结构数据

channel可以传输基本类型的数据如int, string,同时也可以传输struct数据


type Person struct {
    Name    string
    Age     uint8
    Address Addr
}

type Addr struct {
    city     string
    district string
}

/*
测试channel传输复杂的Struct数据
 */
func testTranslateStruct() {
    personChan := make(chan Person, 1)

    person := Person{"xiaoming", 10, Addr{"shenzhen", "longgang"}}
    personChan <- person

    person.Address = Addr{"guangzhou", "huadu"}
    fmt.Printf("src person : %+v \n", person)

    newPerson := <-personChan
    fmt.Printf("new person : %+v \n", newPerson)
}

这里可以看到可以通过channel传输自定义的Person对象,同时一端修改了数据,不影响另一端的数据,也就是说通过channel传递后的数据是独立的。

关闭channel

channel可以进行关闭,例如写的一段关闭了channel,那么读的一端读取时就可以检测读取失败

/*
测试关闭channel
 */
func testClose() {
    ch := make(chan int, 5)
    sign := make(chan int, 2)

    go func() {
        for i := 1; i <= 5; i++ {
            ch <- i
            time.Sleep(time.Second)
        }

        close(ch)

        fmt.Println("the channel is closed")

        sign <- 0

    }()

    go func() {
        for {
            i, ok := <-ch
            fmt.Printf("%d, %v \n", i, ok)

            if !ok {
                break
            }

            time.Sleep(time.Second * 2)
        }

        sign <- 1

    }()

    <-sign
    <-sign
}

合并多个channel的输出

可以将多个channel的数据合并到一个channel进行输出,形成一个消息队列

/**
将多个输入的channel进行合并成一个channel
 */
func testMergeInput() {
    input1 := make(chan int)
    input2 := make(chan int)
    output := make(chan int)

    go func(in1, in2 <-chan int, out chan<- int) {
        for {
            select {
            case v := <-in1:
                out <- v
            case v := <-in2:
                out <- v
            }
        }
    }(input1, input2, output)

    go func() {
        for i := 0; i < 10; i++ {
            input1 <- i
            time.Sleep(time.Millisecond * 100)
        }
    }()

    go func() {
        for i := 20; i < 30; i++ {
            input2 <- i
            time.Sleep(time.Millisecond * 100)
        }
    }()

    go func() {
        for {
            select {
            case value := <-output:
                fmt.Println("输出:", value)
            }
        }
    }()

    time.Sleep(time.Second * 5)
    fmt.Println("主线程退出")
}

通过channel实现退出的通知

定义一个用于退出的channel比如quit,不断执行任务的线程通过select监听quit的读取,当读取到quit中的消息时,退出当前的任务线程,这里是主线程通知任务线程退出。

/*
测试channel用于通知中断退出的问题
 */
func testQuit() {
    g := make(chan int)
    quit := make(chan bool)

    go func() {
        for {
            select {
            case v := <-g:
                fmt.Println(v)
            case <-quit:
                fmt.Println("B退出")
                return
            }
        }
    }()

    for i := 0; i < 3; i++ {
        g <- i
    }
    quit <- true
    fmt.Println("testAB退出")
}

生产者消费者问题

通过channel可以比较方便的实现生产者消费者模型,这里开启一个生产者线程,一个消费者线程,生产者线程往channel中发送消息,同时阻塞,消费者线程轮询获取channel中的消息,
进行处理,然后阻塞,这时生产者线程唤醒继续后面的逻辑,如此便形成了简单的生产者消费者模型。同时生产者在完成了所有的消息发送后,可以通过quit这个channel通知消费者线程退出,
而消费者线程退出时,通知主线程退出,整个程序完成退出。

/**
生产者消费者问题
 */
func testPCB() {
    fmt.Println("test PCB")

    intchan := make(chan int)
    quitChan := make(chan bool)
    quitChan2 := make(chan bool)

    value := 0

    go func() {
        for i := 0; i < 3; i++ {

            value = value + 1
            intchan <- value

            fmt.Println("write finish, value ", value)

            time.Sleep(time.Second)
        }
        quitChan <- true
    }()
    go func() {
        for {
            select {
            case v := <-intchan:
                fmt.Println("read finish, value ", v)
            case <-quitChan:
                quitChan2 <- true
                return
            }
        }

    }()

    <-quitChan2
    fmt.Println("task is done ")
}

输出顺序问题

/*
这个结果输出是1,2, 也可能是2,1, 也可能是2,顺序是不一定的
 */
func testSequnse() {
    ch := make(chan int)

    go func() {
        v := <-ch
        fmt.Println(v)
    }()
    ch <- 1
    fmt.Println("2")
}

上面这个输出结果是什么呢?运行一下会发现,可能是1,2,也可能是2,1, 也可能是2,顺序是不一定的,那么为什么会是这样的,我觉得因为这是两个不同的线程,
它们是独立运行的,当v := <-ch 执行之后,主线程和当前线程都是运行状态(非阻塞),先执行主线程还是新线程的输出就看cpu运行了,所以结果是不确定的。

channel的超时处理

通过time可以实现channel的超时处理,当一个channel读取超过一定时间没有消息到来时,就可以得到超时通知处理,防止一直阻塞当前线程

/*
检查channel读写超时,并做超时的处理
 */
func testTimeout() {
    g := make(chan int)
    quit := make(chan bool)

    go func() {
        for {
            select {
            case v := <-g:
                fmt.Println(v)
            case <-time.After(time.Second * time.Duration(3)):
                quit <- true
                fmt.Println("超时,通知主线程退出")
                return
            }
        }
    }()

    for i := 0; i < 3; i++ {
        g <- i
    }

    <-quit
    fmt.Println("收到退出通知,主线程退出")
}

channel的输入输出类型指定

channel可以在显示指定它是输入型还是输出型的,指定为输入型,则不能使用它输出消息,否则出错编译不通过,同理,输出型不能接受消息输入,
这样可以在编写代码时防止手误写错误输入输出类型而导致程序错误的问题。指定输入输出类型可以在方法参数时设定,那么它只在当前方法中会做输入输出限制,
可看下面实现。

/*
指定channel是输入还是输出型的,防止编写时写错误输入输出,指定了的话,可以在编译时期作错误的检查
 */
func testInAndOutChan() {
    ch := make(chan int)
    quit := make(chan bool)

    //输入型的chan是这种格式的:inChan chan<- int,如果换成输出型的,则编译时会报错
    go func(inChan chan<- int) {
        for i := 0; i < 10; i++ {
            inChan <- i
            time.Sleep(time.Millisecond * 500)
        }
        quit <- true
        quit <- true
    }(ch)

    go func(outChan <-chan int) {
        for {
            select {
            case v := <-outChan:
                fmt.Println("print out value : ", v)
            case <-quit:
                fmt.Println("收到退出通知,退出")
                return
            }
        }
    }(ch)

    <-quit
    fmt.Println("收到退出通知,主线程退出")
}

channel实现并发数量控制

通过设置一个带缓冲数量的的channel来实现最大并发数量,最大并发数量即为缓冲数量,任务开始时想limit这个channel发送消息,
任务执行完成后从这个limit读取消息,这样就可以保证当并发数量达到limit的缓冲数量时,limit <- true 这里会发生阻塞,停止
创建新的线程,知道某个线程执行完成任务后,从limit读取数据,这样就能保证最大并发数量控制在缓冲数量。

/*
测试通过channel来控制最大并发数,来处理事件
 */
func testMaxNumControl()  {
    maxNum := 3
    limit := make(chan bool, maxNum)
    quit := make(chan bool)

    for i:=0; i<100; i++{
        fmt.Println("start worker : ", i)

        limit <- true

        go func(i int) {
            fmt.Println("do worker start: ", i)
            time.Sleep(time.Millisecond * 20)
            fmt.Println("do worker finish: ", i)

            <- limit

            if i == 99{
                fmt.Println("完成任务")
                quit <- true
            }

        }(i)
    }

    <-quit
    fmt.Println("收到退出通知,主程序退出")
}

监听中断信号的channel

可以创建一个signal信号的channel,同时通过signal.Notify来监听os.Interrupt这个中断信号,因此执行到<- quit时就会阻塞在这里,
直到收到了os.Interrupt这个中断信号,比如按Ctrl+C中断程序的时候,主程序就会退出了。当然还可以监听其他信号,例如os.Kill等。
/*
监听中断信号的channel
*/
func testSignal() {
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)

go func() {
    time.Sleep(time.Second * 2)

    number := 0;
    for{
        number++
        println("number : ", number)
        time.Sleep(time.Second)
    }
}()

fmt.Println("按Ctrl+C可退出程序")
<- quit
fmt.Println("主程序退出")

}

channel实现同步控制,生产者消费者模型

开启多个线程做赚钱和花钱的操作,共享读写remainMoney这个剩余金额变量,实现生产者消费者模型

//同步控制模型,生产者模型
var lockChan = make(chan int, 1)
var remainMoney = 1000
func testSynchronize()  {
    quit := make(chan bool, 2)

    go func() {
        for i:=0; i<10; i++{
            money := (rand.Intn(12) + 1) * 100
            go testSynchronize_expense(money)

            time.Sleep(time.Millisecond * time.Duration(rand.Intn(500)))
        }

        quit <- true
    }()

    go func() {
        for i:=0; i<10; i++{
            money := (rand.Intn(12) + 1) * 100
            go testSynchronize_gain(money)

            time.Sleep(time.Millisecond * time.Duration(rand.Intn(500)))
        }

        quit <- true
    }()

    <- quit
    <- quit

    fmt.Println("主程序退出")
}

func testSynchronize_expense(money int)  {
    lockChan <- 0

    if(remainMoney >= money){
        srcRemainMoney := remainMoney
        remainMoney -= money
        fmt.Printf("原来有%d, 花了%d,剩余%d\n", srcRemainMoney, money, remainMoney)
    }else{
        fmt.Printf("想消费%d钱不够了, 只剩%d\n", money, remainMoney)
    }

    <- lockChan
}

func testSynchronize_gain(money int)  {
    lockChan <- 0

    srcRemainMoney := remainMoney
    remainMoney += money
    fmt.Printf("原来有%d, 赚了%d,剩余%d\n", srcRemainMoney, money, remainMoney)

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

推荐阅读更多精彩内容

  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 11,147评论 4 56
  • Go语言并发模型 Go 语言中使用了CSP模型来进行线程通信,准确说,是轻量级线程goroutine之间的通信。C...
    副班长国伟阅读 2,012评论 0 2
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,014评论 11 349
  • “啊~”苏梦染睡眼惺忪从床上爬起,“7:00了我的妈啊,迟到啦!!!”说着用风一般的速度穿衣洗漱,“妈,我走了”...
    粽子_阅读 233评论 0 0
  • 大自然的魅力无法估量 总是引诱我去流浪 列车在铁轨上碾压出远方的回响 白云在蓝天的庇护下尽显张扬 房屋在树林后面时...
    背包独行的灵魂阅读 172评论 0 0