Go语言 goroutine
下面的代码,在for里面调用函数前加上了go之后,使func进行并发执行。
上面的代码如果不加go执行的话,只会在第一次i=0的时候无限循环执行
fmt.Printf("Hello from "+"goroutine %d\n", i)
。如果加上go,但是不加
time.Sleep(time.Minute)
的话,在func执行之前,for文就会结束,导致内部的func来不及执行。
go开启的其实不是线程,而是协程。
轻量级线程
非抢占式多任务处理,由协程主动交出控制权
编译器/解释器/虚拟机层面的多任务(线程是系统级)
多个协程可能在一个或者多个线程上运行
非抢占式
关于非抢占式可以在看一个例子。
这段代码执行以后我们会发现程序死机了。
主要原因有2个
a[i]++
无法主动交出控制权main函数自身也是一个协程,但是
time.Sleep(time.Minute)
因为没有人交出控制权,导致main一直等待。
从之前的代码可以看到,io操作时可以自动交出控制权的,如何手动交出控制权?可以使用runtime.Gosched()
。一般情况下不会用到runtime.Gosched()
。
关于闭包
还有一个点,可以看到for的里面的函数func定义了参数i,每次调用的时候都传入了i。理论上我们应该直接用i的。
如果我们去掉传参的处理执行的话发现会报错。
这个时候我们可以用go run -race goroutine.go
来看一下发生了什么。
出错的原因是因为如果不把i作为参数传给func,就不会形成闭包,在最后一次执行for的时候i会是10,导致a[10]超出范围。你也可以把var a [10]int
改成var a [11]int
这样就不会报错了,但是这很不安全,所以不建议这样做。
协程和普通函数
其他语言的协程支持
go语言的调度器
在go语言里,具体每个协程如何分配到线程里,都是交给调度器来分配,我们不用操心。
goroutine的定义
直接在函数前加入go
不需要再定义时区分是否是异步函数
调度器在合适的点进行切换
使用-race检测数据访问冲突
goroutine的切换点
下面这些点可以参考,但是由于我们无法100%控制,所以只能是参考。
I/O select
channel
等待锁
函数调用(有时)
runtime.Gosched()
Last updated