这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记。
小学的时候见过这么一道非常有趣的题:小明准备午饭,已知淘米5分钟,准备材料10分钟,煮饭40分钟,炒菜20分钟,最短需要多长时间才能完成?如果没有经过社会的毒打,75分钟是脑海中想到的第一个答案,但如果了解到煮饭的同时可以做其他的事情后,才恍然大悟。到了程序设计也一样,网络请求,IO操作,这些都可以在等待的同时做其他的事情, Javascript 和 Python 都拥有关键字
async和await,Golang也有独特的关键字go让开发者操作,这篇文章将结合我的实验和青训营课程来一起探讨。
go 关键字的语法为
go func
其中 func 可以是任意函数,最简单的例子如下:
func main() {
go func() {
fmt.Println("go func")
}()
fmt.Println("main func")
}
或者可以将函数拆分:
func foo() {
fmt.Println("foo func")
}
func main() {
go foo()
fmt.Println("main func")
}
Golang 会生成一个新的 goroutime 运行该函数,但是这样设计会带来一个问题,go 后的函数的和当前函数的输出顺序是不稳定的,甚至有可能会导致当前函数执行完毕后会中止goroutine的运行,也就是看不到 foo func 的输出。因此诞生了几种曲线救国的方案。
1. 在goroutine后使用time.Sleep()函数
虽然这个方法可以在一定程度上起到防止goroutine中止的问题,但如果 goroutine 运行的时长超过 time.Sleep() 的时长,还是会被中止,也只能是躲得了初一躲不过高三。
2. 在goroutine后使用runtime.Gosched()函数
这个方法可以让当前协程挂起,运行其他的goroutine,直至其他的 goroutine 结束后,再继续执行这个协程。如果需要获取goroutine的数据,可以通过传入指针的方式曲线救国。
3. 使用 chan
chan 是Golang独有的一个数据类型,翻译过来是信道,这个是 Golang 中推荐的一个异步传输方式。使用方法如下:
// 声明与赋值
ch := make(chan int)
// 传入参数
ch <- 5
// 传出参数
<- ch
chan 与队列近乎相同,且通过特殊的符号 <- 来传入和传出。而在传出时,当前协程会被阻塞,直至收到值,如果长时间没有收到, Golang 也会非常智能地提醒用户:fatal error: all goroutines are asleep - deadlock!。当然,也可以通过非阻塞的方式传出,譬如:
data, ok :=<- ch
但是这样就还是会出现goroutine提前中止的情况。
4. 使用WaitGroup()函数
我们可以使用协程组的方式控制 Goroutine 的数量,譬如:
var wg = sync.WaitGroup{}
func foo() {
fmt.Println("foo func")
wg.Done()
}
func main() {
wg.Add(1)
go foo()
wg.Wait()
fmt.Println("main func")
}
var 是声明了一个全局变量 wg,类型是sync.WaitGroup ,wg.Add(1) 是说我有1个协程需要执行,wg.Done() 相当于 wg.Add(-1) 意思就是我这个协程执行完了。wg.Wait() 就是告诉主线程要等一下,等协程都执行完再退出。若需要更多的协程运行,需要在了解协程数量后设置 wg 。
这四种曲线救国的方法中,除了第一种,其他就是喜欢哪个用哪个。
这篇文章对 Goroutine 的初探也只是其中的一角,希望能对大家 Golang 的学习有一定的帮助!