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

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

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

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

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

//使用make创建通道

//无缓冲的整型通道
unbuffered := make(chan int) //有缓冲的字符串通道
buffered := make(chan string, 10)

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

//向通道发送值

//有缓冲的字符串通道
buffered := make(chan string, 10) //通过通道发送一个字符串
buffered <- "gopher"

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

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

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

  

(1)无缓冲的通道

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

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

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

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

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

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

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

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

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

示例1:

//如何用无缓冲的通道来模拟2个goroutine间的网球比赛
package main import (
"fmt"
"math/rand"
"sync"
"time"
) //wg用来等待程序结束
var wg sync.WaitGroup func init() {
rand.Seed(time.Now().UnixNano())
} func main() {
//创建一个无缓冲的通道
court := make(chan int) wg.Add(2) //启动两个选手
go player("kebi", court)
go player("maoxian", court) //发球,向通道中发送数据
court <- 1 //等待游戏结束
wg.Wait() } //player模拟一个选手在打网球
func player(name string, court chan int) {
defer wg.Done() for {
//等待数据发过来
ball, ok := <-court
if !ok {
//检测通道是否为false,如果是false表示通道已经关闭
//如果通道被关闭了,就知道
fmt.Printf("Player %s Won\n", name)
return
} //选个随机数,然后用这个数来判断我们是否丢球
n := rand.Intn(100)
if n%13 == 0 {
fmt.Printf("Player %s Missed\n", name) //关闭通道表示已经输了
close(court)
return
} //显式击球数,并加一
fmt.Printf("Player %s Hit %d\n", name, ball)
ball++ court <- ball
}
} /*
Player maoxian Hit 1
Player kebi Hit 2
Player maoxian Hit 3
Player kebi Hit 4
Player maoxian Missed
Player kebi Won
*/

示例2:

//如何用无缓冲的通道来模拟4个goroutine间的接力比赛
package main import (
"fmt"
"sync"
"time"
) var wg sync.WaitGroup func main() {
baton := make(chan int) //为最后一位跑步者将计数加一
wg.Add(1) //第一位跑步者持有接力棒
go Runner(baton) 开始比赛
baton <- 1 wg.Wait()
} func Runner(baton chan int) {
var newRunner int //等待接力棒
runner := <-baton //开始跑步
fmt.Printf("runner %d running With Baton\n", runner) //创建下一位跑步者
if runner != 4 {
newRunner = runner + 1
fmt.Printf("runner %d To the Line\n", newRunner)
go Runner(baton)
} //围绕跑道跑
time.Sleep(100 * time.Millisecond) //比赛结束了吗?
if runner == 4 {
fmt.Printf("runner %d Finished,Race Over\n", runner)
wg.Done()
return
} //将接力棒交给下一位跑步者
fmt.Printf("Runner %d Exchange With Runner %d\n", runner, newRunner) baton <- newRunner
} /*
runner 1 running With Baton
runner 2 To the Line
Runner 1 Exchange With Runner 2
runner 2 running With Baton
runner 3 To the Line
Runner 2 Exchange With Runner 3
runner 3 running With Baton
runner 4 To the Line
Runner 3 Exchange With Runner 4
runner 4 running With Baton
runner 4 Finished,Race Over
*/

  

(2)有缓冲的通道

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

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

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

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

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

//展示如何使用有缓冲的通道和固定数目的goroutine来处理一堆工作
package main import (
"fmt"
"math/rand"
"sync"
"time"
) const (
numberGoroutines = 4 //使用的goroutine的数量
taskLoad = 10 //要处理的工作的数量
) var wg sync.WaitGroup func init() {
rand.Seed(time.Now().Unix())
} func main() {
tasks := make(chan string, taskLoad) //启动goroutine来处理工作
wg.Add(numberGoroutines)
for gr := 1; gr <= numberGoroutines; gr++ {
go worker(tasks, gr)
} //增加一组要完成的工作
for post := 1; post <= taskLoad; post++ {
tasks <- fmt.Sprintf("Task : %d", post)
} //当所有工作处理完毕时关闭通道
close(tasks) wg.Wait()
} func worker(tasks chan string, worker int) {
defer wg.Done() for {
//等待分配工作
task, ok := <-tasks
if !ok {
//通道已空且被关闭
fmt.Printf("Worker: %d : Shutting Down\n", worker)
return
} //开始工作
fmt.Printf("Worker: %d : Started %s\n", worker, task) //随机等待一段时间来模拟工作
sleep := rand.Int63n(100)
time.Sleep(time.Duration(sleep) * time.Millisecond) //显式完成了工作
fmt.Printf("Worker: %d : Completed %s\n", worker, task)
}
} /*
Worker: 4 : Started Task : 4
Worker: 2 : Started Task : 1
Worker: 1 : Started Task : 3
Worker: 3 : Started Task : 2
Worker: 3 : Completed Task : 2
Worker: 3 : Started Task : 5
Worker: 1 : Completed Task : 3
Worker: 1 : Started Task : 6
Worker: 4 : Completed Task : 4
Worker: 4 : Started Task : 7
Worker: 2 : Completed Task : 1
Worker: 2 : Started Task : 8
Worker: 4 : Completed Task : 7
Worker: 4 : Started Task : 9
Worker: 2 : Completed Task : 8
Worker: 2 : Started Task : 10
Worker: 3 : Completed Task : 5
Worker: 3 : Shutting Down
Worker: 1 : Completed Task : 6
Worker: 1 : Shutting Down
Worker: 2 : Completed Task : 10
Worker: 2 : Shutting Down
Worker: 4 : Completed Task : 9
Worker: 4 : Shutting Down
*/

  

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. php安装redis扩展初始化失败解决办法

    错误信息如下: PHP Warning: PHP Startup: redis: Unable to initialize module Module compiled with module API ...

  2. storm 入门原理介绍_AboutYUN

    转自:http://www.aboutyun.com/thread-7394-1-1.html 了解Storm:http://www.aboutyun.com/thread-9547-1-2.html ...

  3. 第二百三十七节,Bootstrap图标菜单按钮组件

    Bootstrap图标菜单按钮组件 学习要点: 1.小图标组件 2.下拉菜单组件 3.按钮组组件 4.按钮式下拉菜单 本节课我们主要学习一下 Bootstrap 的三个组件功能:小图标组件.下拉菜单组 ...

  4. 【vijos】1757 逆序对(dp)

    https://vijos.org/p/1757 有时候自己sb真的是不好说... 我竟然想了半天都没想到这个转移. 我是有多傻.... 我们设f[i][j]表示1~i的排列且逆序对恰好是j的方案数. ...

  5. 《C++程序设计》朝花夕拾

      (以后再也不用破Markdown写东西了,直到它有一个统一的标准,不然太乱了--) 函数签名 int f (int a, int b) ↑ ↑ ↑ ↑ 返回类型 函数名 形 式 参 数 其中,函数 ...

  6. SpringBoot新增监听器Listener

    什么是web监听器? web监听器是一种Servlet中的特殊的类,它们能帮助开发者监听web中的特定事件,比如ServletContext,HttpSession,ServletRequest的创建 ...

  7. AWS系列-申请Redis

    1.1 打开aws控制台,可以直接搜索redis 1.2 进入redis控制面板 点击启动缓存集群(这个只是启动创建的意思,不是启动下面创建好的node.我也不懂为啥翻译过来是这个意思...) 1.3 ...

  8. const在指针中的用法

    一.指向const对象的指针---对象不能修改 方式1 int value1 = 3; const int *p1 = &value1; *p1 = 5; //错误,不能修改const指向对象 ...

  9. ObjC利用正则表达式抓取网页内容(网络爬虫)

    本文转载至 http://www.cocoachina.com/bbs/read.php?tid=103813&fpage=63 在开发项目的过程,很多情况下我们需要利用互联网上的一些数据,在 ...

  10. Android UI开发第三十六篇——使用Volley加载图片列表

    Android开发者可能会使用Universal Image Loader或者Square`s newer Picasso这些第三方的库去处理图片的加载,那么Volley是怎么加载图片列表的呢,这一篇 ...