热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Go学习之Channel总结

Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication)。类型T表示任意的一种类型双向:chan

Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication)。

类型

T表示任意的一种类型

  • 双向: chan T
  • 单向仅发送: chan <-
  • 单向仅接受: <- chan

单向的channel,不仅可以通过声明make(chan <- interface{}) 来创建,还可以通过隐身或显示的通过 chan 来转换,如下

func main() {
channel := make(chan int, 10)
convert(channel)
}
func convert(channel chan<- int) {}

convert函数中,就可以吧channel当成单向输入管道来使用了

既然 双向 chan,既可以接收,也可以发送,为什么还会有单向chan的存在? 我的一个理解便是 权限收敛,例如一个爬虫系统中,有些进程a仅仅负责抓取页面内容,并转发给进程b,那进程a仅需要 单向发送的chan 即可

Blocking

缺省情况下,发送chan或接收chan会一直阻塞着,直到另一方准备好。这种方式可以用来在gororutine中进行同步,而不必使用显示的锁或者条件变量。

如官方的例子中x, y := <-c, <-c这句会一直等待计算结果发送到channel中。以下面例子看一下

func bufferChannel() {
channel := make(chan int)
i := 0
go func(i int) {
fmt.Printf("start goroutine %d\n", i)①
channel <- i
fmt.Printf("send %d to channel\n", i)②
}(i)
time.Sleep(2 * time.Second)
fmt.Println("sleep 2 second")
value := <-channel③
fmt.Println("got ", value)
}

输出结果如下

start goroutine 0
sleep 2 second
got 0
send 0 to channel

可以看出,go func 执行到了①后并没有继续执行②,而是等待③执行完成后,再去执行②,也就可以说明 channel <- i 阻塞了goroutine的继续执行

如果,我不想在这里阻塞,而是我直接把数据放到channel里,等接收方准备好后,到channel中自取自用如何处理,这里就涉及到了另一个概念 buffered channel

buffered channel

我们把程序修改一下

func bufferChannel() {
channel := make(chan int, 1) // 这里加了个参数
i := 0
go func(i int) {
fmt.Printf("start goroutine %d\n", i)①
channel <- i
fmt.Printf("send %d to channel\n", i)②
}(i)
time.Sleep(2 * time.Second)
fmt.Println("sleep 2 second")
value := <-channel③
fmt.Println("got ", value)
}

输出结果

start goroutine 0
send 0 to channel
sleep 2 second
got 0

我们发现go func执行完①之后就执行了②,并没有等待③的执行结束,这就是buffered channel的效果了

我们只需要在make的时候,声明底2个参数,也就是chan的缓冲区大小即可

通过上面的程序可以看出,我们一直在使用③的形成,即<- chan来读取chan中的数据,但是如果有多个goroutine在同时像一个chan写数据,我们除了使用

for {
value <- chan
}

还有什么更优雅的方式吗

for … range

还是上面那个程序,我们使用 for … range 进行一下改造

func bufferChannel() {
channel := make(chan int, 1)
i := 0
go func(i int) {
fmt.Printf("start goroutine %d\n", i)
channel <- i
fmt.Printf("send %d to channel\n", i)
}(i)
time.Sleep(2 * time.Second)
fmt.Println("sleep 2 second")
for value := range channel {
fmt.Println("got ", value)
}
}

这样就可以遍历 channel 中的数据了,但是我们在运行的时候就会发现,哎 这个程序怎么停不下来了?range channel产生的迭代值为Channel中发送的值,它会一直迭代直到channel被关闭,所以 我们goroutine发送完数据后,把channel关闭一下试试,这一次,我们不再进行time.Sleep(2 * time.Second)

func bufferChannel() {
channel := make(chan int, 1)
i := 0
go func(i int) {
fmt.Printf("start goroutine %d\n", i)
channel <- i
fmt.Printf("send %d to channel\n", i)
close(channel)
}(i)
for value := range channel {
fmt.Println("got ", value)
}
}

这样,整个程序就可以正常退出了,所以,在使用range的时候需要注意,如果channel不关闭,则range会一直阻塞在这里的

select

我们上面讲的一直都是只有一个channel的时候,我们应该怎么去做,加入有两个channel或者更多的channel,我们应该怎么去做,这里就介绍一下 go里面的多路复用 select,以下面程序为例

func example() {
tick := time.Tick(time.Second)
after := time.After(3 * time.Second)
for {
select {
case <-tick:
fmt.Println("tick 1 second")
case <-after:
fmt.Println("after 3 second")
return
default:
fmt.Println("come into default")
time.Sleep(500 * time.Millisecond)
}
}
}

time.Tick是go的time包提供的一个定时器的一个函数,它返回一个channel,并在指定时间间隔内,向channel发送一条数据,time.Tick(time.Second)就是每秒钟向这个channel发送一个数据

time.After是go的time包提供的一个定时器的一个函数,它返回一个channel,并在指定时间间隔后,向channel发送一条数据,time.After(3 * time.Second)就是3s后向这个channel发送一个数据

输出结果

come into default
come into default
tick 1 second
come into default
come into default
tick 1 second
come into default
come into default
tick 1 second
after 3 second

可以看到,select会选择一个没有阻塞的 channel,并执行响应 case下的逻辑,这样就可以避免由于一个 channel阻塞而导致后续的逻辑阻塞的情况了

我们继续做个小实验,把上面关闭的channel放到 select里面试一下

func example() {
tick := time.Tick(time.Second)
after := time.After(3 * time.Second)
channel := make(chan int, 1)
go func() {
channel <- 1
close(channel)
}()
for {
select {
case <-tick:
fmt.Println("tick 1 second")
case <-after:
fmt.Println("after 3 second")
return
case value := <- channel:
fmt.Println("got", value)
default:
fmt.Println("come into default")
time.Sleep(500 * time.Millisecond)
}
}
}

输出结果

.
.
.
.
got 0
got 0
got 0
got 0
got 0
after 3 second

简直是车祸现场,幸好设置了3s主动退出,那case的时候,有没有办法判断这个channel是否关闭了呢,当然是可以的,看下面的程序

func example() {
tick := time.Tick(time.Second)
after := time.After(3 * time.Second)
channel := make(chan int, 1)
go func() {
channel <- 1
close(channel)
}()
for {
select {
case <-tick:
fmt.Println("tick 1 second")
case <-after:
fmt.Println("after 3 second")
return
case value, ok := <- channel:
if ok {
fmt.Println("got", value)
} else {
fmt.Println("channel is closed")
time.Sleep(time.Second)
}
default:
fmt.Println("come into default")
time.Sleep(500 * time.Millisecond)
}
}
}

输出结果

come into default
got 1
channel is closed
tick 1 second
channel is closed
channel is closed
after 3 second

综上可以看出,通过 value, ok := <- channel 这种形式,ok获取的就是用来判断channel

是否关闭的,ok为 true,表示channel正常,否则,channel就是关闭的


推荐阅读
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • Oracle分析函数first_value()和last_value()的用法及原理
    本文介绍了Oracle分析函数first_value()和last_value()的用法和原理,以及在查询销售记录日期和部门中的应用。通过示例和解释,详细说明了first_value()和last_value()的功能和不同之处。同时,对于last_value()的结果出现不一样的情况进行了解释,并提供了理解last_value()默认统计范围的方法。该文对于使用Oracle分析函数的开发人员和数据库管理员具有参考价值。 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了logistic回归(线性和非线性)相关的知识,包括线性logistic回归的代码和数据集的分布情况。希望对你有一定的参考价值。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 本文介绍了brain的意思、读音、翻译、用法、发音、词组、同反义词等内容,以及脑新东方在线英语词典的相关信息。还包括了brain的词汇搭配、形容词和名词的用法,以及与brain相关的短语和词组。此外,还介绍了与brain相关的医学术语和智囊团等相关内容。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文介绍了游标的使用方法,并以一个水果供应商数据库为例进行了说明。首先创建了一个名为fruits的表,包含了水果的id、供应商id、名称和价格等字段。然后使用游标查询了水果的名称和价格,并将结果输出。最后对游标进行了关闭操作。通过本文可以了解到游标在数据库操作中的应用。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
author-avatar
gaoyong0713
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有