在Go语言里面,你不仅可以使用原子函数和互斥锁来保证对共享资源的安全访问以消除竞争状态,

还可以使用通道,通过发送和接收需要共享的资源,在goroutine之间做同步。

当一个资源需要在goroutine之间共享时,通道在goroutine之间架起了一个管道,并提供了确保同步交换数据的机制。

声明通道时,需要指定将要被共享的数据类型。可以通过通道共享内置类型、命名类型、结构类型和引用类型的值或者指针。

在Go语言中需要使用内置函数make来创建一个通道。

  1. //使用make创建通道
  2.  
  3. //无缓冲的整型通道
  4. unbuffered := make(chan int)
  5.  
  6. //有缓冲的字符串通道
  7. buffered := make(chan string, 10)

向通道发送值或者指针需要用到<-操作符。

  1. //向通道发送值
  2.  
  3. //有缓冲的字符串通道
  4. buffered := make(chan string, 10)
  5.  
  6. //通过通道发送一个字符串
  7. buffered <- "gopher"

我们创建了一个有缓冲的通道,数据类型是字符串,包含一个10个值的缓冲区。

之后,我们通过通道发送字符串”gopher“。为了让另一个goroutine可以从通道里接收到这个字符串,我们依旧使用<-操作符,但这次是一元操作符。

  1. //从通道里接收一个字符串
  2. value := <-buffered

  

(1)无缓冲的通道

无缓冲通道是指在接收前没有能力保存任何值得通道。

这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收任务。

如果两个giroutine没有同时准备好,通道会导致先执行发送或接收操作的goroutine阻塞等待。

这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在。

下面说明一下无缓冲通道是如何来共享数据的:

两个goroutine(假设为A和B)都到达通道,但哪个都没有开始执行发送或者接收。

假设goroutine A向通道发送了数据,goroutine A会在通道中被锁住,直到交换完成。

goroutine B会从通道里接收数据,goroutine B一样会在通道中被锁住,直到交换完成。

随后会完成数据交换,随后会释放两个goroutine。

示例1:

  1. //如何用无缓冲的通道来模拟2个goroutine间的网球比赛
  2. package main
  3.  
  4. import (
  5. "fmt"
  6. "math/rand"
  7. "sync"
  8. "time"
  9. )
  10.  
  11. //wg用来等待程序结束
  12. var wg sync.WaitGroup
  13.  
  14. func init() {
  15. rand.Seed(time.Now().UnixNano())
  16. }
  17.  
  18. func main() {
  19. //创建一个无缓冲的通道
  20. court := make(chan int)
  21.  
  22. wg.Add(2)
  23.  
  24. //启动两个选手
  25. go player("kebi", court)
  26. go player("maoxian", court)
  27.  
  28. //发球,向通道中发送数据
  29. court <- 1
  30.  
  31. //等待游戏结束
  32. wg.Wait()
  33.  
  34. }
  35.  
  36. //player模拟一个选手在打网球
  37. func player(name string, court chan int) {
  38. defer wg.Done()
  39.  
  40. for {
  41. //等待数据发过来
  42. ball, ok := <-court
  43. if !ok {
  44. //检测通道是否为false,如果是false表示通道已经关闭
  45. //如果通道被关闭了,就知道
  46. fmt.Printf("Player %s Won\n", name)
  47. return
  48. }
  49.  
  50. //选个随机数,然后用这个数来判断我们是否丢球
  51. n := rand.Intn(100)
  52. if n%13 == 0 {
  53. fmt.Printf("Player %s Missed\n", name)
  54.  
  55. //关闭通道表示已经输了
  56. close(court)
  57. return
  58. }
  59.  
  60. //显式击球数,并加一
  61. fmt.Printf("Player %s Hit %d\n", name, ball)
  62. ball++
  63.  
  64. court <- ball
  65. }
  66. }
  67.  
  68. /*
  69. Player maoxian Hit 1
  70. Player kebi Hit 2
  71. Player maoxian Hit 3
  72. Player kebi Hit 4
  73. Player maoxian Missed
  74. Player kebi Won
  75. */

示例2:

  1. //如何用无缓冲的通道来模拟4个goroutine间的接力比赛
  2. package main
  3.  
  4. import (
  5. "fmt"
  6. "sync"
  7. "time"
  8. )
  9.  
  10. var wg sync.WaitGroup
  11.  
  12. func main() {
  13. baton := make(chan int)
  14.  
  15. //为最后一位跑步者将计数加一
  16. wg.Add(1)
  17.  
  18. //第一位跑步者持有接力棒
  19. go Runner(baton)
  20.  
  21. 开始比赛
  22. baton <- 1
  23.  
  24. wg.Wait()
  25. }
  26.  
  27. func Runner(baton chan int) {
  28. var newRunner int
  29.  
  30. //等待接力棒
  31. runner := <-baton
  32.  
  33. //开始跑步
  34. fmt.Printf("runner %d running With Baton\n", runner)
  35.  
  36. //创建下一位跑步者
  37. if runner != 4 {
  38. newRunner = runner + 1
  39. fmt.Printf("runner %d To the Line\n", newRunner)
  40. go Runner(baton)
  41. }
  42.  
  43. //围绕跑道跑
  44. time.Sleep(100 * time.Millisecond)
  45.  
  46. //比赛结束了吗?
  47. if runner == 4 {
  48. fmt.Printf("runner %d Finished,Race Over\n", runner)
  49. wg.Done()
  50. return
  51. }
  52.  
  53. //将接力棒交给下一位跑步者
  54. fmt.Printf("Runner %d Exchange With Runner %d\n", runner, newRunner)
  55.  
  56. baton <- newRunner
  57. }
  58.  
  59. /*
  60. runner 1 running With Baton
  61. runner 2 To the Line
  62. Runner 1 Exchange With Runner 2
  63. runner 2 running With Baton
  64. runner 3 To the Line
  65. Runner 2 Exchange With Runner 3
  66. runner 3 running With Baton
  67. runner 4 To the Line
  68. Runner 3 Exchange With Runner 4
  69. runner 4 running With Baton
  70. runner 4 Finished,Race Over
  71. */

  

(2)有缓冲的通道

有缓冲的通道是一种在被接收前能存储一个或者多个值的通道,这种类型的通道并不强制要求goroutine之间必须同时完成发送和接收。

通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。

只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会被阻塞。

这导致有缓冲的通道和无缓冲的通道之间的一个很大的不同:

无缓冲通道保证进行发送和接收的goroutine会在同一时间进行数据交换,有缓冲的通道没有这种保证。

  1. //展示如何使用有缓冲的通道和固定数目的goroutine来处理一堆工作
  2. package main
  3.  
  4. import (
  5. "fmt"
  6. "math/rand"
  7. "sync"
  8. "time"
  9. )
  10.  
  11. const (
  12. numberGoroutines = 4 //使用的goroutine的数量
  13. taskLoad = 10 //要处理的工作的数量
  14. )
  15.  
  16. var wg sync.WaitGroup
  17.  
  18. func init() {
  19. rand.Seed(time.Now().Unix())
  20. }
  21.  
  22. func main() {
  23. tasks := make(chan string, taskLoad)
  24.  
  25. //启动goroutine来处理工作
  26. wg.Add(numberGoroutines)
  27. for gr := 1; gr <= numberGoroutines; gr++ {
  28. go worker(tasks, gr)
  29. }
  30.  
  31. //增加一组要完成的工作
  32. for post := 1; post <= taskLoad; post++ {
  33. tasks <- fmt.Sprintf("Task : %d", post)
  34. }
  35.  
  36. //当所有工作处理完毕时关闭通道
  37. close(tasks)
  38.  
  39. wg.Wait()
  40. }
  41.  
  42. func worker(tasks chan string, worker int) {
  43. defer wg.Done()
  44.  
  45. for {
  46. //等待分配工作
  47. task, ok := <-tasks
  48. if !ok {
  49. //通道已空且被关闭
  50. fmt.Printf("Worker: %d : Shutting Down\n", worker)
  51. return
  52. }
  53.  
  54. //开始工作
  55. fmt.Printf("Worker: %d : Started %s\n", worker, task)
  56.  
  57. //随机等待一段时间来模拟工作
  58. sleep := rand.Int63n(100)
  59. time.Sleep(time.Duration(sleep) * time.Millisecond)
  60.  
  61. //显式完成了工作
  62. fmt.Printf("Worker: %d : Completed %s\n", worker, task)
  63. }
  64. }
  65.  
  66. /*
  67. Worker: 4 : Started Task : 4
  68. Worker: 2 : Started Task : 1
  69. Worker: 1 : Started Task : 3
  70. Worker: 3 : Started Task : 2
  71. Worker: 3 : Completed Task : 2
  72. Worker: 3 : Started Task : 5
  73. Worker: 1 : Completed Task : 3
  74. Worker: 1 : Started Task : 6
  75. Worker: 4 : Completed Task : 4
  76. Worker: 4 : Started Task : 7
  77. Worker: 2 : Completed Task : 1
  78. Worker: 2 : Started Task : 8
  79. Worker: 4 : Completed Task : 7
  80. Worker: 4 : Started Task : 9
  81. Worker: 2 : Completed Task : 8
  82. Worker: 2 : Started Task : 10
  83. Worker: 3 : Completed Task : 5
  84. Worker: 3 : Shutting Down
  85. Worker: 1 : Completed Task : 6
  86. Worker: 1 : Shutting Down
  87. Worker: 2 : Completed Task : 10
  88. Worker: 2 : Shutting Down
  89. Worker: 4 : Completed Task : 9
  90. Worker: 4 : Shutting Down
  91. */

  

go——通道(二)的更多相关文章

  1. java nio 通道(二)

    本文章来源于我的个人博客: java nio 通道(二) 一,文件通道 文件通道总是堵塞式的,因此不能被置于非堵塞模式. FileChannel对象是线程安全的.多个进程能够在同一个实例上并发调用方法 ...

  2. stm32之ADC应用实例(单通道、多通道、基于DMA)

    文本仅做记录.. 硬件:STM32F103VCT6 开发工具:Keil uVision4 下载调试工具:ARM仿真器 网上资料很多,这里做一个详细的整合.(也不是很详细,但很通俗).  所用的芯片内嵌 ...

  3. stm32之ADC应用实例(单通道、多通道、基于DMA)-转载精华帖,最后一部分的代码是精华

    硬件:STM32F103VCT6    开发工具:Keil uVision4    下载调试工具:ARM仿真器网上资料很多,这里做一个详细的整合.(也不是很详细,但很通俗).所用的芯片内嵌3个12位的 ...

  4. MongoDB分组汇总操作,及Spring data mongo的实现

    转载请在页首注明作者与出处 一:分组汇总 1.1:SQL样例 分组汇总的应用场景非常多,比如查询每个班级的总分是多少,如果用关系形数据库,那么sql是这样子的 ),class from score g ...

  5. luogg_java学习_06_面向对象特性之封装和继承

    这篇博客总结了1天,希望自己以后返回来看的时候理解更深刻,也希望可以起到帮助初学者的作用. 转载请注明 出自 : luogg的博客园 , 因为前不久偶然发现某网站直接复制粘贴我的博客,交谈之后他们修改 ...

  6. Canny算子边缘检测(cvCanny)

    Canny是常用的边缘检测方法,其特点是试图将独立边的候选像素拼装成轮廓. John Canny于1986年提出Canny算子,它与Marr(LoG)边缘检测方法类似,也属于是先平滑后求导数的方法. ...

  7. Android数据存储-文件操作

    一.预备知识 1.Android中的MVC设计模式 MVC (Model-View-Controller):M是指逻辑模型,V是指视图模型,C则是控制器.一个逻辑模型可以对于多种视图模型,比如一批统计 ...

  8. 智能车学习(三)—— ADC学习

    一.代码分享: 1.ADC头文件 #ifndef ADC_H_ #define ADC_H_ #include "common.h" typedef enum { // ----- ...

  9. DataSanp的控制老大-DSServer

    DSServer作用:管理DataSnap服务器生命周期.(启动,停止) 一.方法: 1.BroadcastMessage 向所以客户端发送消息,客户端必须已注册通道. 2.BroadcastObje ...

  10. [OpenCV] IplImage and Functions

    In this chapter, APIs will make U crazy. Good luck! Next, Review Linear Algebra.  Ref: http://blog.c ...

随机推荐

  1. Mtx——Mobile Tutorial Series (LibGDX & MTX)

    http://moribitotechx.blogspot.co.uk/p/tutorial-series-libgdx-mtx.html —————————————————————————————— ...

  2. 快使用阿里云的maven仓库

    自从开源中国的maven仓库挂了之后就一直在用国外的仓库,慢得想要砸电脑的心都有了.如果你和我一样受够了国外maven仓库的龟速下载?快试试阿里云提供的maven仓库,从此不在浪费生命…… 仓库地址: ...

  3. python 的简单抓取图片

    在我们日常上网浏览网页的时候,经常会看到一些好看的图片,我们就希望把这些图片保存下载,或者用户用来做桌面壁纸,或者用来做设计的素材. 我们最常规的做法就是通过鼠标右键,选择另存为.但有些图片鼠标右键的 ...

  4. 邮件正文及其附件的发送的C++实现

     这段代码我花了整整一天来编写,假设转载,请注明出处,谢谢!    前面的一篇文章已经讲了怎样发送邮件正文,原理我就不再叙述了.要了解的同学请到这里查看!    http://blog.csdn.ne ...

  5. java collection 类图

    转载:http://visionsky.blog.51cto.com/733317/371809/

  6. 简单的TableView

    背景知识 每个表都是UITableView的实例,表中的每一行都是UITableViewCell的实例. TableView的种类 Grouped table Plain table without ...

  7. <input>type类型

    当Input框需要输入数字时,一般用到type='number' 但是在输入框有 上下小箭头 google后有解决CSS方案 在chrome下: input::-webkit-outer-spin-b ...

  8. 使用phpnow本地搭建Discuz!如何实现伪静态

    用phpnow本地搭建Discuz!如何实现伪静态 因为phpnow本身就支持伪静态,那只要看下相关的设置是否正确,写个.htaccess的文件就可以了. 一.确认httpd.conf的设置 在xxx ...

  9. 配置管理之PackageProvider接口

     PackageProvider的开始 从前面几章中我们了解到了一点:想知道如何加载相关配置文件就必须去找StrutsXmlConfigurationProvider类和XmlConfiguratio ...

  10. winsxs清理

    win7的网友经常问:C:Windowswinsxs文件夹下是什么文件,可以删除吗? 答案是不建议你删除的,如果你特别想删除的话我推荐你一个工具. 本人亲测系统正常运行. 可以为C盘剩下将近4G空间 ...