个人介绍和该框架的开发背景指路:GO语言实践栏目介绍 - 知乎 (zhihu.com)
golang语言的一个重要特性就是对并发的支持:CSP思想是go语言对并发设计的核心。而刷脚本最重要的就是对并发的支持,如果有10000条数据,使用并行最起码可以使程序加速100倍以上!因此本章将会详细介绍go语言的并发以及在该脚本框架中使如何实现的。
Sync包
Sync包是许多语言都有的工具,在go语言中,除了Sync.once与Sync.WaitGroup以外,其他的模块都被建议使用在较为低级的并发环境,比如说多个协程对某些结构体的并发访问控制。而协程间的通信则更被建议使用channel进行交互。本章不对Sync包做过多介绍,挖个坑,后续对其和源码进行分析。
Channel
channel是什么
channel是CSP思想的核心体现,使用者可以将其理解为两个协程之间通信的管道。
和其他类型类似,channel的声明十分简单,这里以空接口的channel举例,一共有三种channel,双向、只读、只写,双向兼具读写功能。
很多人会在代码中只是用双向channel,但是笔者建议不要滥用双向channel,因为这涉及协程对channel的权限,使用单向channel可以让代码更加清晰。
channel的读写通常有两种模式:
直接读取模式
该模式可以通过ok判断reciveStream是否已经关闭,还有无数据
range模式
range 模式常常用来消费持续传入的数据
此外,channel也可以创建容量, 带缓冲队列的channel将极大影响channel的阻塞机制。
channel的状态查询:
当channel没有设置缓存队列时,如果发送方发送了数据但是接收方没有接受,发送协程将会阻塞。同样,如果接收方没有等到发送方的数据,接收方也会阻塞。类似于一种双向ready的模式。
而带缓冲channel则相当于有个缓存空间,只有发满了,或者读空了,对应的收发方才会阻塞。
此外,channel还可以主动关闭。
在写并发代码时,我们经常会遇到一种情况,代码不知道在哪里卡住了,不知道是哪个协程在发送信号给channel,还是从channel中读取数据阻塞了。这时候,我们可以根据下面的表格进行对应的查询和排查。
操作 | channel状态 | 结果 |
---|---|---|
Read | nil | 阻塞 |
打开且非空 | 输出值 | |
打开且空 | 阻塞 | |
通道关闭 | <默认值>,false | |
只写通道 | 编译错误 | |
Write | nil | 阻塞 |
打开且队列已满 | 阻塞 | |
打开且队列不满 | 正常写入 | |
通道关闭 | panic | |
只读通道 | 编译错误 | |
close | nil | panic |
打开且非空 | 关闭channel,但是能读取,等到通道里没数据了,就会开始读默认值 | |
打开且空 | 正常关闭,读取默认值 | |
通道已关闭 | panic | |
只读通道 | 编译错误 |
该图选择Go语言并发之道,一本极好的go并发书:
channel妙用
使用channel我们可以设计出许多优秀的代码模式,这里仅介绍与该脚本框架相关的模式。
先介绍相关的语法与设计知识,想直接看全部代码解析的可以直接拉到下一部分
for-select
for-select 是重要的通道模式,通常的搭配是一个数据流,如dataStream,表示取到了数据你要做些什么,一个终止channel,表示提前结束这个协程。还有默认流,根据select-case的机制,如果case中的所有流都没有获取到数据,那么他就会堵塞,这时候default可以用来执行一些默认的操作。
pipeline
pipeline是处理流式数据的惯用设计方式,就像流水线一样工作保证每个协程,不会长期空闲。scripttool.go中的runsh采用的就是典型的流水线模式。
扇入
扇入就是将多个数据流复用或合并成一个流,典型的扇入代码如下所示:
multiplex协程将输入的流统一到multiplexStream中,并传出。利用waitgroup并发的进行扇入。
context
context是管理协程的绝佳工具,在golang语言开发中,经常会遇到一个问题,就是如何管理子协程的生命周期。context就有这个功能。详细的context介绍可以查询其他资料,这里仅说明本系统中使用的功能。仅需要注意这段代码中的ctx部分,从上游传来一个ctx,如果上游关闭了ctx,那么select就会接收到ctx.Done(),提前退出函数。利用ctx管理子协程的生命周期,是最为常见的用法之一
整段逻辑
通过前面的说明,这段代码看起来就容易许多了,rowDataStream负责传输文件的每行数据,rushDataRoutine开启了多个协程执行脚本,然后将每个协程的结果扇入给logDataStream,logDataRoutine读取该通道进行日志打印,done通道等待logdataStream执行完再退出主协程。这也是消费-订阅模型的一个基本模板
本期的主要内容就在这里,祝大家学有所得,下期将对超时和重试机制进行说明,干杯!