原文:https://blog.csdn.net/jfkidear/article/details/88661693

------------------------------------------

关于 Goroutine Channel Select 的用法和理解

阅读 384

收藏 13

2017-05-18

原文链接:blog.sodroid.com

转载请注明本文地址,感谢 :)

了解进程、线程、协程

本文不详细解释这三个名词的意思,下面有一篇文章,不懂的同学可以参考看看。

Goroutine的使用

  • Goroutine 奉行通过通信来共享内存,而不是共享内存来通信
  • 使用goroutine很简单,只需要用到一个关键字go,我们用一段代码来示例一下如何使用go关键字
  1.  
    package main
  2.  
    import (
  3.  
    "fmt"
  4.  
    )
  5.  
    func main() {
  6.  
    go Goroutine()
  7.  
    }
  8.  
    func Goroutine() {
  9.  
    fmt.Println("Goroutine")
  10.  
    }

没有输出?

  • 执行了上面的代码后,你会发现,什么都没有输出,那么是什么问题呢?
  • 因为我们当前的程序,只是一个单线程的程序,main函数只要执行完毕后,就不会再管其他线程在做什么事情了,程序自动退出。 然后我们想到了一个办法,加一个sleep函数,让main函数等待Goroutine函数执行完毕后再退出。

  • 更改后的代码如下:

  1.  
    package main
  2.  
    import (
  3.  
    "fmt"
  4.  
    "time"
  5.  
    )
  6.  
    func main() {
  7.  
    go Goroutine()
  8.  
    time.Sleep(3 * time.Second)
  9.  
    }
  10.  
    func Goroutine() {
  11.  
    fmt.Println("Goroutine")
  12.  
    }
  • 不出所料,程序成功打印出了“Goroutine”字符串。

引入Channel

  • 通过上面的实验,我们成功地用Goroutine,并打印出了一句话。现在问题来了,这个并不是我们想要的。为什么?如果我的Goroutine函数里的代码需要执行10s、20s甚至更多的时候,怎么办?难道还是继续Sleep更多的秒数吗?
  • 我们需要的是,当Goroutine函数执行完毕的时候,自动通知main函数。所以这里我们就引出了channel

创建方式

  • channel使用make创建

    • chann := make(chan bool)
  • 往channel中传入数据

    • chann <- true
  • 输出channel中的数据

    • <- chann
  • 接着我们改进上面使用Sleep方式的代码,换成使用channel的版本

  • 代码如下:
  1.  
    package main
  2.  
    import (
  3.  
    "fmt"
  4.  
    )
  5.  
    func main() {
  6.  
     
  7.  
    chann := make(chan bool)
  8.  
    go func() {
  9.  
    fmt.Println("Goroutine")
  10.  
    chann <- true
  11.  
    }()
  12.  
    <- chann
  13.  
    }
  • 如果运行以上的代码片段后,你成功输出了Goroutine,那么说明我们的写法是没错的。现在我们来理解一下这段代码的意思,我们看看匿名函数外面的<-chann,这实际上是起到了阻塞的作用,当匿名函数里面的业务没有执行完并讲true传入channel中的时候,这个程序是不会退出的,匿名函数外面会一直在等待。当匿名函数里面执行到了chann <- true,也就是将true传入了channel的时候,就不会继续阻塞,这时候程序就会结束。这就是channel的作用。

使用for range迭代输出Channel

  • 需要注意的是,当你在对channel 迭代的时候,必须在某个位置明确的关闭这个channel,不然程序就会死锁。我们用一个代码段来示例一下:
  1.  
    package main
  2.  
    import (
  3.  
    "fmt"
  4.  
    )
  5.  
    func main() {
  6.  
    chann := make(chan bool)
  7.  
    go func() {
  8.  
    fmt.Println("Goroutine")
  9.  
    chann <- true
  10.  
    close(chann) //关闭channel
  11.  
    }()
  12.  
    for v := range chann {
  13.  
    fmt.Println(v)
  14.  
    }
  15.  
    }

Channel 的定义类型

  • Channel 类型的定义格式:

    • ChannelType = ("chan" | "chan" "<-" | "<-" "chan")
  • Channel包含了三种类型的定义,可选的<-代表的是Channel的方向。没有指定方向的话,Channel就是双向的,既可以接受数据,也可以发送数据。
    • chan T //可以接收也可以发送
    • chan <- bool //可以发送bool类型的数据
    • <- chan bool //可以接收int类型的数据

无缓冲Channel与缓冲Channel

无缓冲Channel

  • 定义方式 chann := make(chan int)
  • 所谓无缓冲Channel就是在通道中只能传入1个值,若是传入的值一直没有被取走,那么就会一直阻塞着,直到被取走。可以说是同步阻塞的
  • 需要注意的是,无缓冲Channel一定是先取后传。当代码中出现了取的操作时,发现Channel里面并没有值,那么就会发生阻塞,当Channel里有值了,才会停止阻塞。

缓冲Channel

  • 定义方式 chann := make(chan int,100)
  • 有缓冲Channel,比如缓冲值是100,那么除非传入的值已经达到了100了,否则这个Channel中,还是可以不断地传值进去。当Channel满了,就会发生阻塞,等值被取走后,才可以继续传值。

如何判断一组任务是否完成

使用缓冲Channel

  1.  
    package main
  2.  
    import (
  3.  
    "fmt"
  4.  
    "runtime"
  5.  
    )
  6.  
    func main() {
  7.  
    runtime.GOMAXPROCS(runtime.NumCPU()) // 设置线程数为当前计算机的CPU数
  8.  
     
  9.  
    chann := make(chan bool, 10)
  10.  
    for i := 0; i < 10; i++ {
  11.  
    go Go(chann, i)
  12.  
    }
  13.  
    for i := 0; i < 10; i++ {
  14.  
    <-chann
  15.  
    }
  16.  
    }
  17.  
    func Go(chann chan bool, index int) {
  18.  
    a := 1
  19.  
    for i := 0; i < 100000000; i++ {
  20.  
    a += 1
  21.  
    }
  22.  
    fmt.Println(index, a)
  23.  
    chann <- true
  24.  
    }

使用sync包

  • 通过设置一个任务组,大小为10。每完成一个任务就记录一次wg.Done(),当10个任务都完成的时候,程序自动退出。
  1.  
    package main
  2.  
    import (
  3.  
    "fmt"
  4.  
    "runtime"
  5.  
    "sync"
  6.  
    )
  7.  
    func main() {
  8.  
    runtime.GOMAXPROCS(runtime.NumCPU())
  9.  
    wg :=sync.WaitGroup{}
  10.  
    wg.Add(10)
  11.  
    for i := 0; i < 10; i++ {
  12.  
    go Go(&wg, i)
  13.  
    }
  14.  
    wg.Wait()
  15.  
    }
  16.  
    func Go(wg *sync.WaitGroup, index int) {
  17.  
    a := 1
  18.  
    for i := 0; i < 100000000; i++ {
  19.  
    a += 1
  20.  
    }
  21.  
    fmt.Println(index, a)
  22.  
    wg.Done()
  23.  
    }

Select

select 用于多个channel监听并收发消息,当任何一个case满足条件则会执行,若没有可执行的case,就会执行default,如果没有default,程序就会阻塞。

  1.  
    package main
  2.  
    import (
  3.  
    "fmt"
  4.  
    "time"
  5.  
    )
  6.  
    func main() {
  7.  
    chann := make(chan int)
  8.  
    go enqueue(chann)
  9.  
    for {
  10.  
    select {
  11.  
    case v, ok := <-chann:
  12.  
    if ok {
  13.  
    fmt.Println(v)
  14.  
    } else {
  15.  
    fmt.Println("close")
  16.  
    return
  17.  
    }
  18.  
    default:
  19.  
    fmt.Println("waiting")
  20.  
    }
  21.  
    }
  22.  
    }
  23.  
    func enqueue(chann chan int) {
  24.  
    time.Sleep(3 * time.Second)
  25.  
    chann <- 1
  26.  
    close(chann)
  27.  
    }

参考

【转】关于 Goroutine Channel Select 的用法和理解的更多相关文章

  1. Go语言学习笔记(七)杀手锏 Goroutine + Channel

    加 Golang学习 QQ群共同学习进步成家立业工作 ^-^ 群号:96933959 Goroutine Go语言的主要的功能在于令人简易使用的并行设计,这个方法叫做Goroutine,通过Gorou ...

  2. 使用goroutine+channel和java多线程+queue队列的方式开发各有什么优缺点?

    我感觉很多项目使用java或者c的多线程库+线程安全的queue数据结构基本上可以实现goroutine+channel开发能达到的需求,所以请问一下为什么说golang更适合并发服务端的开发呢?使用 ...

  3. HAVING COUNT(*) > 1的用法和理解

    HAVING COUNT(*) > 1的用法和理解 作用是保留包含多行的组. SELECT class.STUDENT_CODE FROM crm_class_schedule class GR ...

  4. golang channel select

    尝试多个channel同时触发时,select的表现: package main import ( "fmt" "time" ) func loop(ch ch ...

  5. GO语言练习:channel select 超时机制

    1.代码 2.运行 3.解析 1.代码 package main import ( "time" "fmt" ) func waitFor(ch chan in ...

  6. 一个Golang例子:for + goroutine + channel

    Rob Pike 在 Google I/O 2012 - Go Concurrency Patterns 里演示了一个例子(daisy chain). 视频地址:https://www.youtube ...

  7. golang并发编程goroutine+channel(一)

    go语言的设计初衷除了在不影响程序性能的情况下减少复杂度,另一个目的是在当今互联网大量运算下,如何让程序的并发性能和代码可读性达到极致.go语言的并发关键词 "go" go dos ...

  8. go14--并发concurrency,Goroutine ,channel

    package main /** 并发concurrency 很多人都是冲着 Go 大肆宣扬的高并发而忍不住跃跃欲试,但其实从 源码的解析来看,goroutine 只是由官方实现的超级“线程池”而已. ...

  9. goroutine,channel

    Go语言中有个概念叫做goroutine, 这类似我们熟知的线程,但是更轻. 以下的程序,我们串行地去执行两次loop函数: package main import "fmt" f ...

随机推荐

  1. Python----数据预处理代码实例

    为方便收藏学习,转载自:https://www.jb51.net/article/158168.htm 本文实例为大家分享了Python数据预处理的具体代码,供大家参考,具体内容如下 1.导入标准库 ...

  2. 【ARM-Linux开发】Wi-Fi 应用工具wpa_supplicant

    wpa_supplicant是一个跨平台的无线安全管理软件,这里需要用它来对无线网络进行配置,wpa_supplicant相关工具已经移植好,包含在我们提供的文件系统中. 配置无线网络 wpa_sup ...

  3. 最新 中钢网java校招面经 (含整理过的面试题大全)

    从6月到10月,经过4个月努力和坚持,自己有幸拿到了网易雷火.京东.去哪儿.中钢网等10家互联网公司的校招Offer,因为某些自身原因最终选择了中钢网.6.7月主要是做系统复习.项目复盘.LeetCo ...

  4. redis的发布与订阅机制

    Redis 发布/订阅机制原理分析 Redis 通过 PUBLISH. SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能. 这些命令被广泛用于构建即时通信应用,比如网络聊天室(c ...

  5. BBC这10部国宝级纪录片,让孩子看遍世间最美的地方

    https://weibo.com/ttarticle/p/show?id=2309404382383649486138#related

  6. K8S从入门到放弃系列-(12)Kubernetes集群Coredns部署

    摘要: 集群其他组件全部完成后我们应当部署集群 DNS 使 service 等能够正常解析,1.11版本coredns已经取代kube-dns成为集群默认dns. 1)下载yaml配置清单 [root ...

  7. Sql 第一行某列减第二行某列

    --1. 将结果插入临时表SELECT *INTO xxx FROM( SELECT TOP 1 a.FQTY,a.fseq FROM T_SAL_ORDERENTRY as a WHERE FQTY ...

  8. docker 实践四:仓库管理

    本篇我们来了解 docker 仓库的内容. 注:环境为 CentOS7,docker 19.03 仓库(Responsitory)是集中存放镜像的地方,又分公共仓库和私有仓库. 注:有时候容易把仓库与 ...

  9. scratch少儿编程——03、动作:运动的开始,游戏的基础。

    各位小伙伴大家好: 从这一期开始我们来学Scratch的具体操作. 第一季我们会从每一个脚本模块开始.一个程序块一个程序块去操作,感受它的效果. 今天我们来一起学习程序区的脚本类动作模块的指令. 动作 ...

  10. IOS+H5页面自定义按钮无效

    在IOS整合H5页面的时候,自定义的按钮失去效果,Android系统可以. 如图,确定和取消按钮在IOS系统无效. 解决办法是在两个按钮上加上一个style属性即可 <span class=&q ...