在阅读本文前,我希望你有一定的Go语言基础,以及一部分关于协程的使用经验。
本文旨在帮助你使用高级并发技巧,其主要包含了以下几个部分:goroutine
的基本用法;使用chan
来实现多个goroutine
之间的通信;使用select
关键字来处理超时等。
术语 | 解析 |
---|---|
goroutine |
指协程,比线程要更轻量级 |
chan /channel |
指管道,多用于多个 goroutine 之间通信 |
一个简单的例子
1 | go复制代码func boring(msg string) { |
1 | rust复制代码I'm listening |
上述这段代码有两个部分,boring
方法负责向控制台输出当前的循环次数,main
方法的第一行为这个方法开启了一个协程,也就是说,main
方法不会等待boring
方法执行完毕。main
方法在输出I'm listening
后,进入为期2秒的睡眠,随后唤醒结束main函数。由于main函数结束会带来整个程序的结束,所以开启的boring
协程也会结束。
不过,上述的例子只是一个简单的演示。实际上,协程之间、协程与主进程之间是需要通信的,这能够帮助我们完成更为复杂的应用。
Go 管道的用法
一个简单的使用方法如下
1 | go复制代码func boring(msg string, c chan string) { |
1 | python复制代码You say: "boring! 0" |
上述方法简单说就是boring
方法在给管道c
发送数据,并且等待另一头,也就是main
方法来消费。由于管道中只能够存在一个数据,所以main
方法和boring
方法在某些程度上是交替运行的。但实际上不完全是,以main
方法来说,接受到管道的数据后可以直接进行下一步,而不需要继续等待。
【知识点】Chan的概念
在Go语言中,通道是goroutine
与另一个goroutine
通信的媒介,并且这种通信是无锁的。换句话说,通道是一种允许一个goroutine
将数据发送到另一个goroutine
的技术。默认情况下,通道是双向的,这意味着goroutine可以通过同一通道发送或接收数据,如下图所示:
在Go语言中,除了chan string
这样的写法能够使用读写功能双向管道外,还可以创建出单向管道,如<-chan string
只能从管道中读取数据,而chan<- string
只能够向管道中写入数据。
【案例讲解】两个线程输出数据
通过两个管道实现
1 | go复制代码// `boring` 是一个返回管道的方法,该管道用于和 `boring` 方法通信 |
这段代码会让代码按照boring("Joe")
、boring("Ahn")
这样交替输出。虽然说是能够交替输出数据,但这个本质上不是通过线程之间的通信实现,为此下面会进行一点改造。
合并管道
1 | go复制代码// `boring` 是一个返回管道的方法,该管道用于和 `boring` 方法通信 |
现在我们可以从两个方法内的协程获取到数据,虽然不能够保证交替输出数据(在这里是随机的),下面,我们使用管道来让多个进程之间开始通信。
协程间通信
1 | go复制代码type Message struct { |
【案例讲解】设定超时等待时间
一个简单实现
1 | go复制代码// `boring` 是一个返回管道的方法,该管道用于和 `boring` 方法通信 |
通过select能够保证在时间到达之后,执行case 2来结束程序。如果刚好二者一起到达,那么会随机执行一个case,在这里case最多可能会执行一次,但不一定来得及输出结果。
【知识点】select 解析
select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。
select 随机执行一个可运行的 case。如果没有 case 可运行,那么会执行 default 里的操作,如果没有 default,那么它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。
【实战】模拟Google搜索服务
Web页面中,搜索是一个很常见的功能,多数情况下,我们会使用一个微服务来搭建一个搜索服务,如ElasticSearch
就是一个单独的服务。在这里,我们不会真的模拟一个ES来处理,反之,我们用一个随机延时的函数来代替它。由于搜索的时间不能够保证,有时候会很快,但有时候也会慢,不管是因为搜索本身就需要时间还是由于IO的耗时。
在这个案例中,我们将会循序渐进来告诉你如何更好的利用goroutine
和chan
来处理这个问题。除此以外,这里还使用了函数式编程
技巧,如果你对这个还不太熟悉,可以先了解一些相关的知识再来继续阅读。
Google搜索1.0
1 | go复制代码type Result string |
1 | sql复制代码[ |
现在,我们可以从Google
方法中获取到结果,但是这一步还远远不够,我们希望调用搜索服务有一个时间上线,如果超时,那么相关的结果就不要了,返回现在已经有的数据。但在此之前,我们还发现Google
方法中,三个查询是顺序调用的,只有前者返回了结果,才能够执行后面的逻辑,这也是为什么返回的时间这么长的原因。基于此,我们首先要将搜索的结果更改为并发执行的。
并发搜索
1 | go复制代码type Result string |
1 | sql复制代码[ |
可以看到,现在的搜索结果只需要100+ms
的时间了,这说明三次搜索的确是并发执行的。
更进一步:加上超时时间
1 | go复制代码type Result string |
结果一:部分逻辑超时
1 | bash复制代码timeout |
结果二:所有逻辑都没有超时
1 | sql复制代码[ |
结语
至此,我已经告诉你一些高级并发编程技巧,但高阶技巧远不止这些,希望这些技巧能给你一些帮助,并带来一些思考。
本文转载自: 掘金