这是我参与青训营的第4篇笔记。
什么是goroutine
在java或者C++中我们要实现并发编程时通常需要维护线程池去调度线程执行任务。Go语言已经将这种并发编程封装得很好,Go语言中的goroutine就是一种封装性良好的技术。goroutine的概念类似于线程,但是goroutine是由Go的运行时调度和管理的。Go程序会智能地将goroutine中的任务合理分配给每个CPU。
因此在Go语言中你不需要自己去写进程、线程、协程等,你只需要goroutine去帮你执行任务即可
使用goroutine
启动单个goroutine
使用时只需要在函数前加上go关键字即可
func hello(){
fmt.Println("asdf")
}
func main() {
go hello() // 启动另外一个goroutine去执行hello函数
fmt.Println("main goroutine done!")
}
这里需要注意的是当main函数返回时goroutine就结束了,所有在main函数中启动的goroutine都会结束
启动多个goroutine
var wg sync.WaitGroup
func hello(i int) {
defer wg.Done() // goroutine结束就登记-1
fmt.Println("Hello Goroutine!", i)
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(1) // 启动一个goroutine就登记+1
go hello(i)
}
wg.Wait() // 等待所有登记的goroutine都结束
}
goroutine与线程
可增长的栈
OS线程一般都有固定的栈内存,一个goroutine的栈内z存在其生命周期开始时只有很小的内存,goroutine的栈不是固定的,它可以根据需求增大和缩小,goroutine栈代销限制可以达到1GB,所以在GO语言中一次性可以创建十万个左右的goroutine
goroutine调度
GPM是GO语言运行时层面的实现,是Go语言自己实现的一套调度系统,区别于OS调度:
- G很好理解,就是个goroutine的数据结构,里面除了存放本goroutine的信息外还有与所在P的绑定等信息
- P管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境(函数指针、堆栈地址、地址边界),P会对自己管理的goroutine队列做一些调度,当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了回会去其他的P的队列里抢任务
- M是Go运行时对OS内核线程的虚拟,M与内核线程一般是一一映射的关系,goroutine最终是要放到M上执行的
P和M一般也是一一对应的,他们关系是P管理着一组G挂载在M上运行。当一个G长久阻塞在一个M上时,运行时会新建一个M,阻塞G所在的P会把其他的G挂载在新建的M上,当旧的G阻塞完成或者认为其已经死亡时回收旧的M
P的个数是通过runtime.GOMAXPROCS设定,Go1.5版本之后默认为物理线程数。在并发量大的时候会增加一些P和M,但不会太多,切换太频繁的话得不偿失
单从线程调度讲,Go语言相比起其他语言的优势在于OS线程是由OS内核来调度的,goroutine则是由Go运行时自己的调度器调度的,这个调度器使用一个称为m:n调度的技术。其一大特点就是goroutine调度是在用户态下完成的,不涉及内核态和用户态的频繁却换,包括内存的分配和释放,都是用户态维护着一块大的内存池,不直接调用系统的malloc函数,成本比调度OS线程低很多
runtime包
runtime.Gosched()
让出CPU时间片,重新等待安排任务
package main
import (
"fmt"
"runtime"
)
func main() {
go func(s string) {
for i := 0; i < 2; i++ {
fmt.Println(s)
}
}("world")
// 主协程
for i := 0; i < 2; i++ {
// 切一下,再次分配任务
runtime.Gosched()
fmt.Println("hello")
}
}
runtime.Goexit()
退出当前协程
package main
import (
"fmt"
"runtime"
)
func main() {
go func() {
defer fmt.Println("A.defer")
func() {
defer fmt.Println("B.defer")
// 结束协程
runtime.Goexit()
defer fmt.Println("C.defer")
fmt.Println("B")
}()
fmt.Println("A")
}()
for {
}
}