热门标签 | 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就是关闭的


推荐阅读
  • 在运行于MS SQL Server 2005的.NET 2.0 Web应用中,我偶尔会遇到令人头疼的SQL死锁问题。过去,我们主要通过调整查询来解决这些问题,但这既耗时又不可靠。我希望能找到一种确定性的查询模式,确保从设计上彻底避免SQL死锁。 ... [详细]
  • 最近遇到了一道关于哈夫曼树的编程题目,需要在下午之前完成。题目要求设计一个哈夫曼编码和解码系统,能够反复显示和处理多个项目,直到用户选择退出。希望各位大神能够提供帮助。 ... [详细]
  • 兆芯X86 CPU架构的演进与现状(国产CPU系列)
    本文详细介绍了兆芯X86 CPU架构的发展历程,从公司成立背景到关键技术授权,再到具体芯片架构的演进,全面解析了兆芯在国产CPU领域的贡献与挑战。 ... [详细]
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • Ihavetwomethodsofgeneratingmdistinctrandomnumbersintherange[0..n-1]我有两种方法在范围[0.n-1]中生 ... [详细]
  • 本文总结了在SQL Server数据库中编写和优化存储过程的经验和技巧,旨在帮助数据库开发人员提升存储过程的性能和可维护性。 ... [详细]
  • 驱动程序的基本结构1、Windows驱动程序中重要的数据结构1.1、驱动对象(DRIVER_OBJECT)每个驱动程序会有唯一的驱动对象与之对应,并且这个驱动对象是在驱 ... [详细]
  • 一关于t1表和testtb的索引设计二把主键放到二级索引的后面,会否占据更多的物理空间?三InnoDB的主键该如何选择,业务ID和自增 ... [详细]
  • 图数据库与传统数仓实现联邦查询使用CYPHER实现从关系数据库过滤时间序列指标一、MySQL得到研报实体在Oracle中的唯一ID二、Oracle中过滤时间序列数据三、CYPHER ... [详细]
  • C语言是计算机科学和编程领域的基石,许多初学者在学习过程中会感到困惑。本文将详细介绍C语言的基本概念、关键语法和实用示例,帮助你快速上手C语言。 ... [详细]
  • 本文详细介绍了 com.apollographql.apollo.api.internal.Optional 类中的 orNull() 方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。 ... [详细]
  • 在多线程并发环境中,普通变量的操作往往是线程不安全的。本文通过一个简单的例子,展示了如何使用 AtomicInteger 类及其核心的 CAS 无锁算法来保证线程安全。 ... [详细]
  • 本文详细介绍了数据库并发控制的基本概念、重要性和具体实现方法。并发控制是确保多个事务在同时操作数据库时保持数据一致性的关键机制。文章涵盖了锁机制、多版本并发控制(MVCC)、乐观并发控制和悲观并发控制等内容。 ... [详细]
  • 在什么情况下MySQL的可重复读隔离级别会导致幻读现象? ... [详细]
  • 在机器学习领域,深入探讨了概率论与数理统计的基础知识,特别是这些理论在数据挖掘中的应用。文章重点分析了偏差(Bias)与方差(Variance)之间的平衡问题,强调了方差反映了不同训练模型之间的差异,例如在K折交叉验证中,不同模型之间的性能差异显著。此外,还讨论了如何通过优化模型选择和参数调整来有效控制这一平衡,以提高模型的泛化能力。 ... [详细]
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社区 版权所有