前言
对于抖音项目中的视频相关接口包含很多信息,例如视频的播放链接,视频的作者信息。作者信息中又包含是否关注作者。这些信息不能一次性封装完成(可能是不懂)。所以选择先查询视频信息,然后遍历视频信息补充作者信息,以及通过视频信息获取视频下载链接。很明显,如果单纯的使用for遍历效率是很慢的,实测本地环境请求一次视频流接口的响应时间来到了1.7s,非常不理想。于是想到通过协程来加快封装速度。
goroutine
Go语言中使用goroutine非常简单,只需要在调用函数的时候在前面加上go关键字,就可以为一个函数创建一个goroutine。
一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数。
例如:
func hello() {
fmt.Println("Hello Goroutine!")
}
func main() {
go hello() // 启动另外一个goroutine去执行hello函数
fmt.Println("main goroutine done!")
}
但是如果我们直接启动会发现执行结果只打印了main goroutine done!,并没有打印Hello Goroutine!。
这是因为在程序启动时,Go程序就会为main()函数创建一个默认的goroutine。当main()函数返回的时候该goroutine就结束了,所有在main()函数中启动的goroutine会一同结束。
我们可以使用time.Sleep(time.Second)阻塞当前main函数,让它等一等,这样确实能达到目的,重新运行项目会发现Hello Goroutine!也确实被打印出来了。
但是,我们在项目中不可能使用这样的代码,首先,对于不清楚有多少条数据我们不清楚等待多长时间合适,更多的情况下是多于实际需要的。那么我们应该怎么处理多个并发并且让后面的程序等待呢?
sync.WaitGroup
在代码中生硬的使用time.Sleep肯定是不合适的,Go语言中可以使用sync.WaitGroup来实现并发任务的同步。 sync.WaitGroup有以下几个方法:
| 方法名 | 功能 |
|---|---|
| (wg * WaitGroup) Add(delta int) | 计数器+delta |
| (wg *WaitGroup) Done() | 计数器-1 |
| (wg *WaitGroup) Wait() | 阻塞直到计数器变为0 |
sync.WaitGroup内部维护着一个计数器,计数器的值可以增加和减少。例如当我们启动了N 个并发任务时,就将计数器值增加N。每个任务完成时通过调用Done()方法将计数器减1。通过调用Wait()来等待并发任务执行完,当计数器值为0时,表示所有并发任务已经完成。
示例代码:
var wg sync.WaitGroup
func hello() {
defer wg.Done()
fmt.Println("Hello Goroutine!")
}
func main() {
wg.Add(1)
go hello() // 启动另外一个goroutine去执行hello函数
fmt.Println("main goroutine done!")
wg.Wait()
}
我们在处理视频信息封装时,使用for循环,但内部使用goroutine进行封装,并且调用wg.Add(1),这样可以保证后续操作是在视频信息封装完成后进行的。
实测使用协程并发封装后,视频流的请求响应时间为170ms,极大的提升了响应速度。