女主宣言

提到Go语言,最为亮点的特性,也就是goroutine的协程了。协程中我们用的最多的无非是channel,小编最近也在研究Go语言的一些特性。但是对于channel理解不是很深,所以特地整理了本篇文章来深入理解一下channel的行为,跟大家分享一下。

PS:丰富的一线技术、多元化的表现形式,尽在“HULK一线技术杂谈”,点关注哦!

1

简介

当我第一次开始使用Go的channel的时候,我犯了一个错误,认为channel是一个数据结构。我将channel看作是在goroutine之间提供自动同步访问的队列。这种结构上的理解使我编写了许多糟糕而复杂的并发代码。

随着时间的推移,我逐渐了解到,最好的办法是忘掉channel的结构,关注它们的行为。所以提到channel,我想到了一个概念:信号。一个通道允许一个goroutine向另一个goroutine发出特定事件的信号。信号是应该使用channel做的一切的核心。将channel看作一种信号机制,可以让你编写具有明确定义和更精确的行为的代码。

要理解信号是如何工作的,我们必须理解它的三个属性:

  • 交付保证

  • 状态

  • 有数据或无数据

这三个属性共同构成了围绕信号的设计理念。 在讨论这些属性之后,我们将提供一些代码示例,这些示例演示如何使用这些属性进行信号传递。

2

交付保证

交付保证是基于一个问题:“我是否需要保证由特定的goroutine发送的信号已经被收到了?”

换句话说,我们可以给出下面这个示例:

01 go func() {

02     p := <-ch // 接收

03 }()

04

05 ch <- "paper" // 发送

执行发送的goroutine是否需要保证,通过第5行被发送的一份报告(paper),在继续执行之前,被第2行要接收的goroutine接收到了?

根据这个问题的答案,你会知道使用两种channel中的哪一种:无缓冲或缓冲。每种channel在交付保证时提供不同的行为。

保证是很重要的。比如,如果你没有生活保证时,你不会紧张吗?在编写并发软件时,对是否需要保证有一个重要的理解是至关重要的。随着我们的继续,你将学会如何做出决定。

3

状态

channel的行为直接受其当前状态的影响。channel的状态可以为nilopenclosed

以下示例将介绍,如何在这三个状态中声明或设置一个channel。

// ** nil channel

// 如果声明为零值的话,将会是nil状态

var ch chan string

// 显式的赋值为nil,设置为nil状态

ch = nil

// ** open channel

// 使用内部函数make创建的channel,为open状态

ch := make(chan string)    // ** closed channel

// 使用close函数的,为closed状态

close(ch)

状态决定了发送接收操作的行为。

信号通过一个channel发送和接收。不可以称为读/写,因为channel不执行输入/输出。

当一个channel为nil状态时,channel上的任何发送或接收都将被阻塞。当为open状态时,信号可以被发送和接收。如果被置为closed状态的话,信号不能再被发送,但仍有可能接收到信号。

4

有数据或无数据

需要考虑的最后一个信号属性是,信号是否带有数据。

通过在channel上执行发送带有数据的信号。

01 ch <- "paper"

当你用数据发出信号时,通常是因为:

  • goroutine被要求开始一项新任务。

  • goroutine报告了一个结果。

通过关闭一个channel来发送没有数据的信号。

01 close(ch)

当发送没有数据信号的时候,通常是因为:

  • goroutine被告知要停止他们正在做的事情。

  • goroutine报告说已经完成,没有结果。

  • goroutine报告说它已经完成了处理,并且关闭。

没有数据的信号传递的一个好处是,一个单一的goroutine可以同时发出很多的信号。而在goroutines之间,用数据发送信号通常是一对一之间的交换。

有数据信号

当要使用数据进行信号传输时,您可以根据需要的担保类型选择三种channel配置选项。

这三个channel选项是无缓冲,缓冲>1或缓冲=1。

  • 有保证

    • 因为信号的接收在信号发送完成之前就发生了。

    • 一个没有缓冲的通道可以保证发送的信号已经收到。

  • 无保证

    • 因为信号的发送是在信号接收完成之前发生的。

    • 一个大小>1的缓冲通道不能保证发送的信号已经收到。

  • 延迟保证

    • 因为第一个信号的接收,在第二个信号发送完成之前就发生了。

    • 一个大小=1的缓冲通道为您提供了一个延迟的保证。它可以保证发送的前一个信号已经收到。

缓冲区的大小绝不是一个随机数,它必须是为一些定义好的约束而计算出来的。在计算中没有无穷远,所有的东西都必须有一个明确定义的约束,无论是时间还是空间。

无数据信号

没有数据的信号,主要是为取消而预留的。它允许一个goroutine发出信号,让另一个goroutine取消他们正在做的事情,然后继续前进。取消可以使用非缓冲和缓冲channel来实现,但是在没有数据发送的情况下使用缓冲channel会更好。

内置函数 close 用于在没有数据的情况下发出信号。正如状态一节中介绍的,你仍然可以在一个关闭的通道接收到信号。事实上,在一个关闭的channel上的任何接收都不会阻塞,接收操作总是返回。

在大多数情况下,您希望使用标准库 context 包来实现无数据的信号传递。context 包使用一个没有缓冲的channel来进行信号传递,而内置函数 close 发送没有数据的信号。

如果选择使用自己的通道进行取消,而不是 context 包,那么你的通道应该是 chan struct{} 类型的。这是一种零空间的惯用方式,用来表示一个仅用于信号传输的channel。

场景

有了这些属性,进一步了解它们在实践中工作的最佳方式就是运行一系列的代码场景。

有数据信号 - 保证 - 无缓冲的channel

当你需要知道发送的信号已经收到时,就会有两种情况出现。一种是等待任务,另一种是等待结果。

场景 1 - 等待任务

设想你是一名经理,并雇佣了一名新员工。在这个场景中,你希望你的新员工执行一个任务,但是他们需要等待,直到你准备好。这是因为你需要在他们开始之前给他们一份报告(paper)。

01 func waitForTask() {

02     ch := make(chan string)

03

04     go func() {

05         p := <-ch

06

07         // 员工执行工作

08

09         // 员工可以自由地去做

10     }()

11

12     time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)

13

14     ch <- "paper"

15 }

在02行中,无缓冲的channel被创建,string 类型的数据将被发送到信号中。然后在04行,一名员工被雇佣,并被告知在05行前等待你的信号,然后再做他们的工作。第05行是channel接收,导致员工在等待你发送的文件时阻塞。一旦员工收到了这份报告,员工就完成了工作,然后就可以自由地离开了。

你作为经理正在和你的新员工一起工作。因此,当你在第04行雇佣了员工后,你会发现自己(在第12行)做了你需要做的事情来解阻塞并且通知员工。值得注意的是,不知道要花费多长的时间来准备这份报告(paper)。

最终你准备好给员工发信号了。在第14行,你执行一个带有数据的信号,数据就是那份报告(paper)。由于使用了一个没有缓冲的channel,所以当你的发送操作完成后,你就得到了该雇员已经收到该文件的保证。接收发生在发送之前。

场景2 - 等待结果

在接下来的场景中,事情发生了反转。这一次,你希望你的新员工在被雇佣的时候立即执行一项任务,你需要等待他们工作的结果。你需要等待,因为在你可以继续之前,你需要他们的报告(paper)。

01 func waitForResult() {

02     ch := make(chan string)

03

04     go func() {

05         time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)

06

07         ch <- "paper"

08

09         // 员工已经完成了,并且可以自由地离开

10     }()

11

12     p := <-ch

13 }

在第02行中,创建了一个没有缓冲的channel,该channel的属性是 string 型数据将被发送到信号。然后在第04行,一名雇员被雇佣,并立即被投入工作。当你在第04行雇佣了这名员工后,你会发现自己排在第12行,等待着这份报告。

一旦工作由第05行中的员工完成,他们就会在第07行通过有数据的channel发送结果给你。由于这是一个没有缓冲的通道,所以接收在发送之前就发生了,并且保证你已经收到了结果。一旦员工有了这样的保证,他们就可以自由地工作了。在这种情况下,你不知道他们要花多长时间才能完成这项任务。

成本/效益

一个没有缓冲的通道可以保证接收到的信号被接收。这很好,但没有什么是免费的。这种担保的成本是未知的延迟。在等待任务场景的过程中,员工不知道要花多长时间才能发送那份报告。在等待结果的情况下,你不知道需要多长时间才能让员工发送结果。在这两种情况下,这种未知的延迟是我们必须要面对的,因为需要保证。如果没有这种保证行为,逻辑是行不通的。

以下场景请大家结合以上内容,具体分析查看。

有数据信号 - 无保证 - 缓冲的 channel > 1

01 func fanOut() {

02     emps := 20

03     ch := make(chan string, emps)

04

05     for e := 0; e < emps; e++ {

06         go func() {

07             time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)

08             ch <- "paper"

09         }()

10     }

11

12     for emps > 0 {

13         p := <-ch

14         fmt.Println(p)

15         emps--

16     }

17 }

01 func selectDrop() {

02     const cap = 5

03     ch := make(chan string, cap)

04

05     go func() {

06         for p := range ch {

07             fmt.Println("employee : received :", p)

08         }

09     }()

10

11     const work = 20

12     for w := 0; w < work; w++ {

13         select {

14             case ch <- "paper":

15                 fmt.Println("manager : send ack")

16             default:

17                 fmt.Println("manager : drop")

18         }

19     }

20

21     close(ch)

22 }

有数据信号 - 延迟保证 - 缓冲channel 1

01 func waitForTasks() {

02     ch := make(chan string, 1)

03

04     go func() {

05         for p := range ch {

06             fmt.Println("employee : working :", p)

07         }

08     }()

09

10     const work = 10

11     for w := 0; w < work; w++ {

12         ch <- "paper"

13     }

14

15     close(ch)

16 }

无数据信号 - 上下文(Context)

01 func withTimeout() {

02     duration := 50 * time.Millisecond03

04     ctx, cancel := context.WithTimeout(context.Background(), duration)

05     defer cancel()

06

07     ch := make(chan string, 1)

08

09     go func() {

10         time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)

11         ch <- "paper"

12     }()

13

14     select {

15     case p := <-ch:

16         fmt.Println("work complete", p)

17

18     case <-ctx.Done():

19         fmt.Println("moving on")

20     }

21 }

总结

在使用channel(或并发)时,关于保证、状态和发送的信号的属性非常重要。它们将帮助指导你实现你正在编写的并发程序和算法所需的最佳行为。它们将帮助你找到bug,并找出潜在的糟糕代码。

在这篇文章中,我们分享了一些示例程序,它们展示了在不同场景中信号的属性是如何工作的。每个规则都有例外,但是这些模式是开始的良好基础。

扫描下方二维码了解更多内容

聊一聊Go中channel的行为相关推荐

  1. 聊一聊计算机视觉中常用的注意力机制 附Pytorch代码实现

    聊一聊计算机视觉中常用的注意力机制以及Pytorch代码实现 注意力机制(Attention)是深度学习中常用的tricks,可以在模型原有的基础上直接插入,进一步增强你模型的性能.注意力机制起初是作 ...

  2. 小师妹学JavaIO之:NIO中Channel的妙用

    文章目录 简介 Channel的分类 FileChannel Selector和Channel DatagramChannel SocketChannel ServerSocketChannel As ...

  3. java mongo hint_聊一聊mongodb中的 explain 和 hint

    原标题:聊一聊mongodb中的 explain 和 hint 看到explain和hint的时候,第一个想到的就是mysql,确实,这就是在mysql中借鉴过来的,既然是借鉴过来的,我想大家都知道这 ...

  4. 聊一聊数学中的基本定理(三)——代数基本定理

    早点关注我,精彩不错过! 在前面两篇文章中,我们聊透了算术基本定理的证明和意义,相关内容请戳: 聊一聊数学中的基本定理(二)--算术基本定理的价值 聊一聊数学中的基本定理(一)--算术基本定理的证明 ...

  5. 聊一聊前端中常说的接口

    文章目录 聊一聊前端中常说的接口 1. 接口是前端提供的还是后端提供的 2. 什么是接口 3. 总结 聊一聊前端中常说的接口 平时总是听到前端中的接口这样的词,接下来简单说一下我对前端接口一些浅见. ...

  6. golang 中 channel 的详细使用、使用注意事项及死锁分析

    什么是 channel 管道 它是一个数据管道,可以往里面写数据,从里面读数据. channel 是 goroutine 之间数据通信桥梁,而且是线程安全的. channel 遵循先进先出原则. 写入 ...

  7. golang中channel使用

    1 golang中channel使用 文章目录 1 golang中channel使用 1.1 channel介绍 1.2 channel使用 1.2.1 channel声明和初始化 1.2.2 cha ...

  8. golang中Channel通道(二)

    golang中Channel通道(二) 一.带缓冲和不带缓冲的通道的区别 1.非缓冲通道 一次发送操作对应一次接收操作,对于一个goroutine来讲,它的一次发送,在另一个goroutine接收之前 ...

  9. channel java_Java中channel用法总结

    本文实例总结了Java中channel用法.分享给大家供大家参考.具体分析如下: 1.Channel接口的定义: public interface Channel { public boolean i ...

最新文章

  1. Android之从网络上获取图片的两种方式讲解:thread+handle和AsyncTask方式
  2. 前端学习(3061):vue+element今日头条管理-接口分页参数说明
  3. 拼多多:永远不会对孵化品牌“二选一” 扶持千家工厂触达4.4亿消费者
  4. sensor_msgs::PointCloud2转换pcl::PCLPointCloud2 pcl::PointXYZ
  5. 四年级计算机病毒与网络安全,《计算机病毒与网络安全》教学案例
  6. bootstrap创建响应式网站
  7. 苹果系统连接服务器打印机,Mac系统怎么连接打印机
  8. Excel中VBA合并工作表
  9. printf二进制数据
  10. Appscan安全测试
  11. 《muduo网络库》学习笔记——时间轮Timeing wheel
  12. http文件上传到web服务器,上传到ftp服务器
  13. Google圈钱新法:为小网站提供廉价搜索
  14. jlink怎么调试linux程序_【转】ubuntu linux下openocd + gdb-insight 用Jlink调试arm程序
  15. 银行信用风险预测分析
  16. paddle百度飞浆入门使用教程
  17. Gitee Pages Pro + Hexo自定义域名
  18. 毕业一年的计科人,聊一下毕业一年的前端开发心酸历程(很学zha的那种,不喜就走)
  19. mongoose 连接数据库
  20. 计算机网络功能ppt,计算机网络的组与功能.ppt

热门文章

  1. SWF反编神器Action Script Viewer终身免费升级!
  2. 后序遍历(非递归)☆
  3. mysql数据库根目录恢复_MySQL中数据导入恢复的简单教程
  4. 查询SQLSERVER执行过的SQL记录(历史查询记录)
  5. 运用大数据助力大发展
  6. Datrium公司以几近疯狂的方式提升速度表现
  7. 定义软件定义的存储市场
  8. 很容易学习的JQuery库 : (八) 杂项 noConflict() 方法
  9. java Future FutureTask 并发操作
  10. chkdsk 和sfc.exe修复命令