【入门系列】Golang Goroutine初探 | 青训营笔记

145 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记。

小学的时候见过这么一道非常有趣的题:小明准备午饭,已知淘米5分钟,准备材料10分钟,煮饭40分钟,炒菜20分钟,最短需要多长时间才能完成?如果没有经过社会的毒打,75分钟是脑海中想到的第一个答案,但如果了解到煮饭的同时可以做其他的事情后,才恍然大悟。到了程序设计也一样,网络请求,IO操作,这些都可以在等待的同时做其他的事情, Javascript 和 Python 都拥有关键字 asyncawait ,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.WaitGroupwg.Add(1) 是说我有1个协程需要执行,wg.Done() 相当于 wg.Add(-1) 意思就是我这个协程执行完了。wg.Wait() 就是告诉主线程要等一下,等协程都执行完再退出。若需要更多的协程运行,需要在了解协程数量后设置 wg

这四种曲线救国的方法中,除了第一种,其他就是喜欢哪个用哪个。

这篇文章对 Goroutine 的初探也只是其中的一角,希望能对大家 Golang 的学习有一定的帮助!