Golang, 以17个简短代码片段,切底弄懂 channel 基础

(原创出处为本博客:http://www.cnblogs.com/linguanh/)

前序:

  因为打算自己搞个基于Golang的IM服务器,所以复习了下之前一直没怎么使用的协程、管道等高并发编程知识。发现自己的channel这块,也就是管道,实在是有些混乱。然后对着文档,边参照官网例子和在编译器测试,总结了下面这17个例子,设置为简短的片段,是为了免得混淆太多,阻碍理解。内含注释丰富,复制粘贴就能编译使用。

  这里立个 flag,有错误欢迎指出,只要你跟着敲完这17个例子,channel的基础绝对可以掌握!

基本概念:

  关于管道 Channel:

    Channels用来同步并发执行的函数并提供它们某种传值交流的机制。

    Channels的一些特性:通过channel传递的元素类型、容器(或缓冲区)和传递的方向由“<-”操作符指定。

    c<-123,把值123输入到管道 c,<-c,把管道 c 的值读取到左边,value :=<-c,这样就是读到 value里面。

  管道分类:

    无缓冲的与有缓冲channel有着重大差别,那就是一个是同步的 一个是非同步的。

   比如

   c1:=make(chan int)         无缓冲

   c2:=make(chan int,1)      有缓冲

   例如:c1<-1

      无缓冲: 不仅仅是向 c1 通道放 1,而是一直要等有别的携程 <-c1 接手了这个参数,那么c1<-1才会继续下去,要不然就一直阻塞着。

      有缓冲: c2<-1 则不会阻塞,因为缓冲大小是1(其实是缓冲大小为0),只有当放第二个值的时候,第一个还没被人拿走,这时候才会阻塞。

例子s

  演示 无缓存 和 有缓冲 的 channel 的样子

  1. 1 func test0(){
  2. 2 /** 演示 无缓存 和 有缓冲 的 channel 的样子 */
  3. 3 done := make(chan bool) /** 无缓冲 */
  4. 4 done1 := make(chan bool,1) /** 有缓冲 */
  5. 5 println(done,done1)
  6. 6 }

  演示 无缓冲在同一个main里面的 死锁例子

  1. 1 func test1() {
  2. 2 /** 编译错误 deadlock,阻死 main 进程 */
  3. 3 /** 演示 无缓冲在同一个main里面的 死锁例子 */
  4. 4 done := make(chan bool)
  5. 5 done<-true /** 这句是输入值,它会一直阻塞,等待读取 */
  6. 6 <-done /** 这句是读取,但是在上面已经阻死了,永远走不到这里 */
  7. 7 println("完成")
  8. 8 }

  

  1. 演示仅有 输入 语句,但没 读取语句 的死锁例子
  1. 1 func test2() {
  2. 2 /** 编译错误 deadlock,阻死 main 进程 */
  3. 3 /** 演示仅有 输入 语句,但没 读取语句 的死锁例子 */
  4. 4 done := make(chan bool)
  5. 5 done<-true /** 输入,一直等待读取,哪怕没读取语句 */
  6. 6 println("完成")
  7. 7 }
  1. 演示仅有 读取 语句,但没 输入语句 的死锁例子
  1. 1 func test3() {
  2. 2 /** 编译错误 deadlock,阻死 main 进程 */
  3. 3 /** 演示仅有 读取 语句,但没 输入语句 的死锁例子 */
  4. 4 done := make(chan bool)
  5. 5 <-done /** 读取输出,前面没有输入语句,done 是 empty 的,所以一直等待输入 */
  6. 6
  7. 7 println("完成")
  8. 8 }
  1. 演示,协程的阻死,不会影响 main
  1. 1 func test4() {
  2. 2 /** 编译通过 */
  3. 3 /** 演示,协程的阻死,不会影响 main */
  4. 4 done := make(chan bool)
  5. 5 go func() {
  6. 6 <-done /** 一直等待 */
  7. 7 }()
  8. 8 println("完成")
  9. 9 /**
  10. 10 * 控制台输出:
  11. 11 * 完成
  12. 12 */
  13. 13 }
  1. test4 的基础上,无缓冲channel在协程 go routine 里面阻塞死
  1. 1 func test5() {
  2. 2 /** 编译通过 */
  3. 3 /** 在 test4 的基础上,无缓冲channel在协程 go routine 里面阻塞死 */
  4. 4 done := make(chan bool)
  5. 5 go func() {
  6. 6 println("我可能会输出哦") /** 阻塞前的语句 */
  7. 7 done<-true /** 这里阻塞死,但是上面那句有可能输出,见 test3 的结论 */
  8. 8 println("我永远不会输出")
  9. 9 <-done /** 这句也不会走到,除非在别的协程里面读取,或者在 main */
  10. 10 }()
  11. 11 println("完成")
  12. 12 }
  1. 编译通过,在 test5 的基础上演示,延时 main 的跑完
  1. 1 func test6() {
  2. 2 /** 编译通过,在 test5 的基础上演示,延时 main 的跑完 */
  3. 3 done := make(chan bool)
  4. 4 go func() {
  5. 5 println("我可能会输出哦")
  6. 6 done<-true /** 这里阻塞死 */
  7. 7 println("我永远不会输出")
  8. 8 <-done /** 这句也不会走到 */
  9. 9 }()
  10. 10 time.Sleep(time.Second * 1) /** 加入延时 1 秒 */
  11. 11 println("完成")
  12. 12 /**
  13. 13 * 控制台输出:
  14. 14 * 我可能会输出哦
  15. 15 * 完成
  16. 16 */
  17. 17 /**
  18. 18 * 结论:
  19. 19 * 如果在 go routine 中阻塞死,也可能不会把阻塞语句前的内容输出,
  20. 20 * 因为main已经跑完了,所以延时一会,等待 go routine
  21. 21 */
  22. 22 }
  1. 演示无缓冲channel 不同的位置里面接收填充和接收
  1. 1 func test7() {
  2. 2 /** 编译通过,演示无缓冲channel 在 不同的位置里面接收填充和接收*/
  3. 3 done := make(chan bool)
  4. 4 go func() {
  5. 5 done<-true /** 直到,<-done 执行,否则这里阻塞死 */
  6. 6 println("我永远不会输出,除非 <-done 执行")
  7. 7
  8. 8 }()
  9. 9 <-done /** 这里接收,在输出完成之前,那么上面的语句将会走通 */
  10. 10 println("完成")
  11. 11 /**
  12. 12 * 控制台输出:
  13. 13 * 我永远不会输出,除非 <-done 执行
  14. 14 * 完成
  15. 15 */
  16. 16 }
  1. 演示无缓冲channel 在不同地方接收的影响
  1. 1 func test8() {
  2. 2 /** 编译通过,演示无缓冲channel 在不同地方接收的影响 */
  3. 3 done := make(chan bool)
  4. 4 go func() {
  5. 5 done<-true /** 直到,<-done 执行,否则这里阻塞死 */
  6. 6 println("我永远不会输出,除非 <-done 执行")
  7. 7 }()
  8. 8 println("完成")
  9. 9 <-done /** 这里接收,在输出完成之后 */
  10. 10 /**
  11. 11 * 控制台输出:
  12. 12 * 完成
  13. 13 * 我永远不会输出,除非 <-done 执行
  14. 14 */
  15. 15 }
  1. 没缓存的 channel 使用 close 后,不会阻塞
  1. 1 func test9() {
  2. 2 /** 编译通过 */
  3. 3 /** 演示,没缓存的 channel 使用 close 后,不会阻塞 */
  4. 4 done := make(chan bool)
  5. 5 close(done)
  6. 6 //done<-true /** 关闭了的,不能再往里面输入值 */
  7. 7 <-done /** 这句是读取,但是在上面已经关闭 channel 了,不会阻死 */
  8. 8 println("完成")
  9. 9 }
  1. 没缓存的 channel,在 go routine 里面使用 close 后,不会阻塞
  1. 1 func test10() {
  2. 2 /** 编译通过 */
  3. 3 /** 演示,没缓存的 channel,在 go routine 里面使用 close 后,不会阻塞 */
  4. 4 done := make(chan bool)
  5. 5 go func() {
  6. 6 close(done)
  7. 7 }()
  8. 8 //done<-true /** 关闭了的,不能再往里面输入值 */
  9. 9 <-done /** 这句是读取,但是在上面已经关闭 channel 了,不会阻死 */
  10. 10 println("完成")
  11. 11 }
  1. 有缓冲的 channel 不会阻塞的例子
  1. 1 func test11() {
  2. 2 /** 编译通过 */
  3. 3 /** 有缓冲的 channel 不会阻塞的例子 */
  4. 4 done := make(chan bool,1)
  5. 5 done<-true
  6. 6 <-done
  7. 7 println("完成")
  8. 8 }
  1. 有缓冲的 channel 会阻塞的例子
  1. 1 func test12() {
  2. 2 /** 编译通过 */
  3. 3 /** 有缓冲的 channel 会阻塞的例子 */
  4. 4 done := make(chan bool,1)
  5. 5 // done<-true /** 注释这句 */
  6. 6 <-done /** 虽然是有缓冲的,但是在没输入的情况下,读取,会阻塞 */
  7. 7 println("完成")
  8. 8 }
  1. 有缓冲的 channel 会阻塞的例子
  1. 1 func test13() {
  2. 2 /** 编译不通过 */
  3. 3 /** 有缓冲的 channel 会阻塞的例子 */
  4. 4 done := make(chan bool,1)
  5. 5 done<-true
  6. 6 done<-false /** 放第二个值的时候,第一个还没被人拿走,这时候才会阻塞,根据缓冲值而定 */
  7. 7 println("完成")
  8. 8 }
  1. 有缓冲的 channel 不会阻塞的例子
  1. 1 func test14() {
  2. 2 /** 编译通过 */
  3. 3 /** 有缓冲的 channel 不会阻塞的例子 */
  4. 4 done := make(chan bool,1)
  5. 5 done<-true /** 不会阻塞在这里,等待读取 */
  6. 6
  7. 7 println("完成")
  8. 8 }
  1. 有缓冲的channel,如果在 go routine 中使用,一定要做适当的延时,否则会输出来不及,因为main已经跑完了,所以延时一会,等待 go routine
  1. 1 func test15() {
  2. 2 /** 编译通过 */
  3. 3 /** 有缓冲的channel 在 go routine 里面的例子 */
  4. 4 done := make(chan bool,1)
  5. 5 go func() {
  6. 6 /** 不会阻塞 */
  7. 7 println("我可能会输出哦")
  8. 8 done<-true /** 如果把这个注释,也会导致 <-done 阻塞 */
  9. 9 println("我也可能会输出哦")
  10. 10 <-done
  11. 11 println("别注释 done<-true 哦,不然我就输出不了了")
  12. 12 }()
  13. 13 time.Sleep(time.Second * 1) /** 1秒延时,去掉就可能上面的都不会输出也有可以输出,routine 调度 */
  14. 14 println("完成")
  15. 15 /**
  16. 16 * 控制台输出:
  17. 17 * 我可能会输出哦
  18. 18 * 我也可能会输出哦
  19. 19 * 完成
  20. 20 */
  21. 21 /**
  22. 22 * 结论:
  23. 23 * 有缓冲的channel,如果在 go routine 中使用,一定要做适当的延时,否则会输出来不及,
  24. 24 * 因为main已经跑完了,所以延时一会,等待 go routine
  25. 25 */
  26. 26 }
  1. channel模式
  1. 1 func getMessagesChannel(msg string, delay time.Duration) <-chan string {
  2. 2 c := make(chan string)
  3. 3 go func() {
  4. 4 for i := 1; i <= 3; i++ {
  5. 5 c <- fmt.Sprintf("%s %d", msg, i)
  6. 6 time.Sleep(time.Millisecond * delay) /** 仅仅起到,下一次的 c 在何时输入 */
  7. 7 }
  8. 8 }()
  9. 9 return c
  10. 10 }
  11. 11
  12. 12 func test16() {
  13. 13 /** 编译通过 */
  14. 14 /** 复杂的演示例子 */
  15. 15 /** 多channel模式 */
  16. 16 c1 := getMessagesChannel("第一", 600 )
  17. 17 c2 := getMessagesChannel("第二", 500 )
  18. 18 c3 := getMessagesChannel("第三", 5000)
  19. 19
  20. 20 /** 层层限制阻塞 */
  21. 21 /** 这个 for 里面会造成等待输入,c1 会阻塞 c2 ,c2 阻塞 c3 */
  22. 22 /** 所以它总是,先输出 c1 然后是 c2 最后是 c3 */
  23. 23 for i := 1; i <= 3; i++ {
  24. 24 /** 每次循环提取一轮,共三轮 */
  25. 25 println(<-c1) /** 除非 c1 有输入值,否则就阻塞下面的 c2,c3 */
  26. 26 println(<-c2) /** 除非 c2 有输入值,否则就阻塞下面的 c3 */
  27. 27 println(<-c3) /** 除非 c3 有输入值,否则就阻塞进入下一轮循环,反复如此 */
  28. 28 }
  29. 29 /**
  30. 30 * 这个程序的运行结果,首轮的,第一,第二,第三 很快输出,因为
  31. 31 * getMessagesChannel 函数的延时 在 输入值之后,在第二轮及其之后
  32. 32 * 因为下一个 c3 要等到 5秒后才能输入,所以会阻塞第二轮循环的开始5秒,如此反复。
  33. 33 */
  34. 34 /** 修改:如果把 getMessagesChannel 里面的延时,放在输入值之前,那么 c3 总是等待 5秒 后输出 */
  35. 35 }
  1. test15 基础修改的,复杂演示例,多channel 的选择,延时在输入之后的情况
  1. 1 func test17() {
  2. 2 /** 编译通过 */
  3. 3 /** 在 test15 基础修改的,复杂演示例子 */
  4. 4 /** 多channel 的选择,延时在输入之后的情况 */
  5. 5 c1 := getMessagesChannel("第一", 600 )
  6. 6 c2 := getMessagesChannel("第二", 500 )
  7. 7 c3 := getMessagesChannel("第三", 5000)
  8. 8 /** 3x3 次循环,是 9 */
  9. 9 /** select 总是会把最先完成输入的channel输出,而且,互不限制 */
  10. 10 /** c1,c2,c3 每两个互不限制 */
  11. 11 for i := 1; i <= 9; i++ {
  12. 12 select {
  13. 13 case msg := <-c1:
  14. 14 println(msg)
  15. 15 case msg := <-c2:
  16. 16 println(msg)
  17. 17 case msg := <-c3:
  18. 18 println(msg)
  19. 19 }
  20. 20 }
  21. 21 /**
  22. 22 * 这个程序的运行结果:
  23. 23 * 第二 1,第三 1,第一 1,第二 2,第一 2,第二 3,第一 3,第三 2,第三 3
  24. 24 */
  25. 25 /** 分析:前3次输出,“第一”,“第二”,“第三”,都有,而且
  26. 26 * 是随机顺序输出,因为协程的调度,第4,5,6次,由于“第二”只延时 500ms,
  27. 27 * 比 600ms 和 5000ms 都要小,那么它先输出,然后是“第一”,此时“第三”还不能输出,
  28. 28 * 因为它还在等5秒。此时已经输出5次,再过 500ms,"第三"的5秒还没走完,所以继续输出"第一",
  29. 29 * 再过 100ms,500+100=600,"第二"也再完成了一次,那么输出。至此,"第一"和"第二"已经
  30. 30 * 把管道的 3 个值全部输出,9-7 = 2,剩下两个是 "第三"。此时,距离首次的 5000ms 完成,
  31. 31 * 还有,500-600-600 = 3800ms,达到后,"第三" 将输出,再过5秒,最后一次"第三输出"
  32. 32 */
  33. 33 }

channel Golang的更多相关文章

  1. 由浅入深剖析 go channel

    原文:https://www.jianshu.com/p/24ede9e90490 ---------------------------------- 由浅入深剖析 go channel chann ...

  2. go并发之goroutine和channel,并发控制入门篇

    并发的概念及其重要性 这段是简单科普,大佬可以跳过 并发:并发程序指同时进行多个任务的程序.在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行 ...

  3. go 简介与包

    简介 Go语言是一种新的语言,一种并发的.带垃圾回收的.快速编译的语言.它具有以下特点: 1.它可以在一台计算机上用几秒钟的时间编译一个大型的Go程序. 2.Go语言为软件构造提供了一种模型,它使依赖 ...

  4. Golang, 以17个简短代码片段,切底弄懂 channel 基础

    (原创出处为本博客:http://www.cnblogs.com/linguanh/) 前序: 因为打算自己搞个基于Golang的IM服务器,所以复习了下之前一直没怎么使用的协程.管道等高并发编程知识 ...

  5. 【GoLang】golang context channel 详解

    代码示例: package main import ( "fmt" "time" "golang.org/x/net/context" ) ...

  6. golang channel 用法转的

    一.Golang并发基础理论 Golang在并发设计方面参考了C.A.R Hoare的CSP,即Communicating Sequential Processes并发模型理论.但就像John Gra ...

  7. 深入学习golang(2)—channel

    Channel 1. 概述 “网络,并发”是Go语言的两大feature.Go语言号称“互联网的C语言”,与使用传统的C语言相比,写一个Server所使用的代码更少,也更简单.写一个Server除了网 ...

  8. golang的goroutine与channel

    Golang的goroutine是非抢占式的, 令人相当蛋疼! 有痛不能呻吟...只能配合channel在各goroutine之间传递信号来实现抢占式, 而这形成了golang最灵活与最具性能的核心. ...

  9. golang中channel的超时处理

    并发中超时处理是必不可少的,golang没有提供直接的超时处理机制,但可以利用select机制来解决超时问题. func timeoutFunc() { //首先,实现并执行一个匿名的超时等待函数 t ...

随机推荐

  1. c#编程基础之枚举

    枚举的意义就在于限制变量取值范围. 当可以确定的几种取值时才可以用. 如果输入一个字符串需要进行判断是否是我们需要的字符串时,则一般需要这样写: using System; using System. ...

  2. Node.js大众点评爬虫

    大众点评上有很多美食餐馆的信息,正好可以拿来练练手Node.js. 1. API分析 大众点评开放了查询商家信息的API,这里给出了城市与cityid之间的对应关系,链接http://m.api.di ...

  3. TeamCity : 配置 Build 过程

    Build 过程往往是比较复杂的,因此 TeamCtiy 通过 build 步骤的方式让您可以实现不同的应用场景.您可以在每个 build 步骤中只做一件事情,然后把一系列的 build 步骤组织起来 ...

  4. awk使用说明

    原文地址:http://www.cnblogs.com/verrion/p/awk_usage.html Awk使用说明 运维必须掌握的三剑客工具:grep(文件内容过滤器),sed(数据流处理器), ...

  5. 遭遇Web print

    一直都知道Web打印还不太成熟,以前IE横行时,普遍都是采用打印相关的ActiveX控件,有些国产厂家做得不错,只是那时还没有付费能力,没有太多关注.而纯粹基于Web标准的打印,浏览器对CSS pri ...

  6. php函数强大的 strtotime

    使用strtotime可以将各种格式的时间字符串转换为时间戳 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 转换常规时间格式 echo date('Y-m-d H:i: ...

  7. Eclipse "Unable to install breakpoint due to missing line number attributes..."

    Eclipse 无法找到 该 断点,原因是编译时,字节码改变了,导致eclipse无法读取对应的行了 1.ANT编译的class Eclipse不认,因为eclipse也会编译class.怎么让它们统 ...

  8. gRPC源码分析0-导读

    gRPC是Google开源的新一代RPC框架,官网是http://www.grpc.io.正式发布于2016年8月,技术栈非常的新,基于HTTP/2,netty4.1,proto3.虽然目前在工程化方 ...

  9. 【干货分享】前端面试知识点锦集04(Others篇)——附答案

    四.Others部分 技术类 1.http状态码有哪些?分别代表是什么意思? (1).成功2×× 成功处理了请求的状态码.200 服务器已成功处理了请求并提供了请求的网页.204 服务器成功处理了请求 ...

  10. 【原】低版本MyEclipse整合高版本Tomcat

    [使用工具] 1.MyEclipse_6.0.1GA_E3.3.1_FullStackInstaller 2.Tomcat 7.0 [问题描述] 直接在MyEclipse中整合,因为这个版本的MyEc ...