使用sync.WaitGroup和go 组合,实现多线程
可增长的栈
OS线程一般都有固定的栈内存(通常为2MB),一个gorutine的栈在其生命周期开始时只有很小的栈(典型情况下2KB),goroutine的栈不是固定的,它可以按需增大和缩小,goroutine的栈大小限制可以达到1GB,虽然极少会用到这么大。所以在Go语言中,一次创建十万左右的goroutine也是可以的。
goroutine调度
GMP是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统。区别于操作系统调度OS线程。
G很好理解,就是goroutine的,里面除了存放本goroutine信息外,还有与所在的P的绑定等信息
M(machine)是Go运行时岁操作系统内核线程的虚拟,M与内核线程一般是一一映射的关系,一个goroutine最终是要放到M上执行的。
P管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境(函数指针,堆栈地址,以及地址边界),
P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停。运行后续的goroutine等)当自己的队列消费完了就会去全局队列里取,如果全局队列里也消费完了就会去其他P的队列里抢任务。
P 的个数通过runtime.GOMAXPROCS设定(最大256),Go1.5版本后默认为物理核心数
单从线程调度讲,Go语言相比起其他语言的有事在于OS线程是由OS内核来调度的,goroutine则是由Go运行时自己的调度器调度的,这个调度器使用一个称为m:n调度的技术。其一大特点是goroutine的调度是在用户态下完成的,不是耳机内核态与用户之间的频繁切换,包括内存的分配与释放,都是在用户态维护着一块大的内存池,不直接调用系统的malloc函数(除非内存池需要改变),成本比调度OS线程低很多。另一方面充分利用了多核的硬件 资源,近似的把若干goroutine均分在物理线程上,再加上本身goroutine的超轻量,以上种种保证了go调度方面的性能。