前言:

上文中我们采用了【原子函数】已经【共享锁】两种方式分别对多个goroutine进行了同步,但是在go语言中提供了另一种更好的方式,那就是使用通道(Channel)。

一、通道是什么?

其实无论是原子函数还是共享锁都是通过共享内存的方式进行的同步、效率一般不高,而Go语言中则使用了通道,它是一种通过传递信息的方式进行数据同步,通过发送和接收需要共享的资源,在goroutine 之间做同步。可以把通道看作是Goroutine之间的桥梁。

例1:创建一个通道

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

通道分为有缓冲和无缓冲的通道。

创建一个Channel的关键点:1.使用make创建 2.使用chan来告诉make我要创建的是通道 3.要告诉通道我要建立什么类型的通道。

例2:向通道发送值和接受值

// 有缓冲的字符串通道
buffered := make(chan string, )
// 通过通道发送一个字符串
buffered <- "Gopher"
// 从通道接收一个字符串
value := <-buffered

这个例子中创建了一个string类型的Channel,并向通道内传递了一个“Gopher”字符串,这里是通过<-进行传入的,然后通过<-这个方式把值放到value当中。

这里我的理解 <-就好比是一个赋值符号,无论是把值传递到Channel中,还是把Channel中的值传出来,都是将右边的值给左边

二、通道的种类

由上面的例如1,可以看到Channel也是有多种的,分为无缓冲通道和有缓冲通道,下面就简单总结一下两种类型的通道。

1.无缓冲通道

无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何值的通道。这种类型的通道要求发送goroutine 和接收goroutine 同时准备好,才能完成发送和接收操作。

上面的图很好的解释了通道和Goroutine的关系

1.左右两个goroutine都没有将手放到通道中。

2.左边的Goroutine将手放到了通道中,模拟了将数据放入通道,此时goroutine会被锁住

3.右边的Goroutine也将手放到了通道中,模拟了从通道中取出数据,同样进入了通道也会被锁住

4.两者通过通道执行数据的交换

5.交换完成

6.两者将手从通道中拿出,模拟了被锁住的goroutine被释放

下面这个程序,模拟了两个人打网球,很好的模拟了两个协程间通过channel进行数据交换

package ChannelDemo

import (
  "fmt"
  "math/rand"
  "sync"
  "time"
) var wg sync.WaitGroup func init() {
rand.Seed(time.Now().UnixNano())
} func PlayTennis() {
court := make(chan int)
wg.Add()
//启动了两个协程,一个纳达尔一个德约科维奇
go player("纳达尔", court)
go player("德约科维奇", court) //将1放到通道中,模拟开球
court <-
wg.Wait()
} func player(name string, court chan int) {
defer wg.Done()
for {
// 将数据从通道中取出
ball, ok := <-court
if !ok {
fmt.Printf("选手 %s 胜利\n", name)
return
} //获取一个随机值,如果可以整除13,就让一个人没有击中,进而关闭整个通道
n := rand.Intn()
if n% == {
fmt.Printf("选手 %s 没接到\n", name)
close(court)
return
}
//如果击中球,就将击球的数量+1,放回通道中
fmt.Printf("选手 %s 击中 %d\n", name, ball)
ball++
court <- ball
}
}

执行结果(每次会有变化):

选手 纳达尔 击中
选手 德约科维奇 击中
选手 纳达尔 击中
选手 德约科维奇 击中
选手 纳达尔 击中
选手 德约科维奇 击中
选手 纳达尔 击中
选手 德约科维奇 击中
选手 纳达尔 没接到
选手 德约科维奇 胜利

ok 标志是否为false。如果这个值是false,表示通道已经被关闭,游戏结束。

下面这个例子,模拟里一个接力赛,也就是协程之间的传递的另一种形式

package ChannelDemo

import (
"fmt"
"sync"
"time"
) var runnerWg sync.WaitGroup func Running() {
//创建一个“接力棒”,也就是通道
baton := make(chan int)
runnerWg.Add()
//创建第一个跑步走
go Runner(baton)
//开始跑
baton <-
runnerWg.Wait()
} func Runner(baton chan int) {
var newRunner int //选手接过接力棒
runner := <-baton
fmt.Printf("第 %d 选手接棒 \n", runner) //如果不是第四名选手,那么说明比赛还在继续
if runner != {
//创建一名新选手
newRunner = runner +
fmt.Printf("第 %d 准备接棒 \n", newRunner)
go Runner(baton)
} //模拟跑步
time.Sleep( * time.Millisecond)
//如果第四名跑完了,就结束
if runner == {
fmt.Printf("第 %d 结束赛跑 \n", runner)
runnerWg.Done()
return
} fmt.Printf("第 %d 选手和第 %d 选手交换了接力棒 \n",
runner,
newRunner) //选手递出接力棒
baton <- newRunner
}

运行结果:

第  名选手接棒
第 名选手准备接棒
第 名选手将接力棒递给第 名选手
第 名选手接棒
第 名选手准备接棒
第 名选手将接力棒递给第 名选手
第 名选手接棒
第 名选手准备接棒
第 名选手将接力棒递给第 名选手
第 名选手接棒
第 名选手冲线,比赛结束

三、无缓冲通道小结

我在看例子的过程中,其实遇到的问题在于,我没有理解goroutine是怎么进行交换的,我以为是goroutine有一个集合一样的结构在通道外面等待取数据,这样就存在我刚拿完再那的情况。就像下面这个图显示一样

但是实际情况应该像下面

Go1写入通道锁住的Go1、Go2读出进入通道锁住Go2,只有Go1写完Go2取完才能释放,但是像上面第一个例子代码,读出之后马上就写入,所以对于这样的协程其实一直是锁住的状态。两个协程就通过这种方式进行数据的传递。

Go语言的通道(1)-无缓冲通道的更多相关文章

  1. Golang并发编程有缓冲通道和无缓冲通道(channel)

    无缓冲通道 是指在接收前没有能力保存任何值得通道.这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作.如果两个goroutine没有同时准备好,通道会导 ...

  2. golang channel无缓冲通道会发生阻塞的验证

    公司搞了午间技术par,本周我讲的主题是关于无缓冲通道channel是否会发生阻塞,并进行了验证. go语言中channel分为无缓冲通道和有缓冲通道两种 channel提供了一种在goroutine ...

  3. [Go] golang无缓冲通道实现工作池控制并发

    展示如何使用无缓冲的通道创建一个goroutine池,控制并发频率1.无缓冲通道保证了两个goroutine之间的数据交换2.当所有的goroutine都忙的时候,能够及时通过通道告知调用者3.无缓冲 ...

  4. [GO]无缓冲通道(unbuffered channel)

    无缓冲通道(unbuffered channel)是指在接收前没有能力保存任何值的通道,在之前的例子中使用的都是无缓冲通道,需要注意的是,对于无缓冲通道而言,不管是往通道里写数据还是从通道里读数据,都 ...

  5. golang中为何在同一个goroutine中使用无缓冲通道会导致死锁

    package main import "fmt" func main() { /* 以下程序会导致死锁 c := make(chan int) c <- 10 n1 := ...

  6. go无缓冲通道

    package main import ( "fmt" "math/rand" "sync" "time" ) //wg ...

  7. go语言之进阶篇无缓冲channel

    1.无缓冲channel 示例: package main import ( "fmt" "time" ) func main() { //创建一个无缓存的ch ...

  8. Go语言有缓冲和无缓冲通道实现样例

    感觉可以,但不好用. 应该有封装程序更高的包包吧. package main import ( "math/rand" "fmt" "time&quo ...

  9. go之无缓冲channel(通道)和有缓冲channel(通道)

    channel我们先来看一下通道的解释:channel是Go语言中的一个核心类型,可以把它看成管道.并发核心单元通过它就可以发送或者接收数据进行通讯,这在一定程度上又进一步降低了编程的难度.chann ...

随机推荐

  1. vue关闭代码检查eslint

    confing/index.js 文件里面的 useEslint 改成false就可以关闭代码检查了   不过为了代码的规范性,不建议关闭!

  2. 逛csdn看见的一个知识阶梯,感觉不错

    逛csdn看见的一个知识阶梯,感觉不错: 计算机组成原理 →  DOS命令 → 汇编语言 → C语言(不包括C++).代码书写规范 → 数据结构.编译原理.操作系统 → 计算机网络.数据库原理.正则表 ...

  3. Android为TV端助力linux命令

    从命令行push到系统目录,用户组是root,而代码里面的是个人用户组,所以要把你push进去的东西改成跟你APK一样的用户组,并且chmod -R 777 文件名修改文件的权限 修改用户组chown ...

  4. python 类继承演示范例的代码

    把做工程过程重要的代码片段备份一次,下面的资料是关于python 类继承演示范例的代码. # a simple example of a class inheritance # tested with ...

  5. Redis进阶学习笔记

    Redis是什么这里不用再说了吧?下面是官方的解释 Redis is an open source (BSD licensed), in-memory data structure store, us ...

  6. .net core 验证 Options 参数

    .net core 中通过 PostConfigure 验证 Options 参数 Intro 在 .net core 中配置项推荐用 Options 来实现,有一些参数可能必须是用由用户来配置,不能 ...

  7. Docker+Redis镜像的原理以及部署安装(超详解附截图)

    文章来源:公众号-智能化IT系统. 一. DOCKER介绍 Docker简介 (1)Docker 是一个开源的应用容器引擎,基于 Go 语言,并遵从Apache2.0协议开源. (2)Docker 可 ...

  8. python正则表达式相关记录

    1 python中字符串前加‘r’,即可阻止‘\’导致的字符转义.但是在re.sub()中参数中加'r'不会起作用.

  9. Saltstack_使用指南07_远程执行-执行模块

    1. 主机规划 远程执行教程文档 https://docs.saltstack.com/en/latest/topics/tutorials/modules.html 所有模块文档 https://d ...

  10. WPF中在MVVM模式下,后台绑定ListCollectionView事件触发问题

    问题:WPF中MVVM模式下 ListView绑定ListCollectionView时,CurrentChanged无法触发 解决方案: 初期方案:利用ListView的SelectionChang ...