goroutine是go语言的协程,go语言在语言和编译器层面提供对协程的支持。goroutine跟线程一个很大区别就是线程是操做系统的对象,而goroutine是应用层实现的线程。goroutine其实是运行在线程池上的,由go的runtime实现调度,goroutine调度时,因为不须要像线程同样涉及到系统调用,要进行用户态和内核态的切换,所以,goroutine被称为轻量级的线程,开销要比线程小不少。然而,这里我想到了一个问题,线程是由操做系统进行调度的,操做系统有对处理器的调度权限,所以线程在上下文切换时,操做系统能够从正在占用处理器的线程手中剥夺处理器的使用权,然而goroutine该怎么完成这个操做呢?性能
然而goroutine并不能像线程的调度那样,goroutine调度时,必须由当前正在占用CPU的goroutine主动让出CPU给新的goroutine,才能完成切换操做。spa
具体实现是这样的,go对全部的系统调用进行了封装,当前执行的goroutine若是正在执行系统调用或者可能会致使当前goroutine阻塞的操做时,runtime就会把当前这个goroutine切换掉。所以一个颇有意思的事情就发生了,若是当前的goroutine没有出现上述的可能会致使goroutine切换的条件时,就能够一直占用CPU(实际上只是一直占用线程),并且并不会由于这个goroutine占用时间太长而进行切换。咱们能够经过以下这段代码进行验证:操作系统
1 package main 2 3 import ( 4 "fmt" 5 "sync" 6 ) 7 8 func process(id int) { 9 fmt.Printf("id: %d\n", id) 10 for { 11 } 12 } 13 func main() { 14 var wg sync.WaitGroup 15 n := 10 16 wg.Add(n) 17 for i := 0; i < n; i++ { 18 go process(i) 19 } 20 wg.Wait() 21 }
这段代码输出以下:线程
id: 9
id: 5
id: 6
id: 0code
按照正常的逻辑,这段代码应该会输出0到9一共十个id,然而执行后发现,只输出了四个(GOMAXPROCS: goroutine底层线程池最大线程数,默认为硬件线程数)id,这就说明实际只有四个goroutine获得了CPU,并且没有进行切换,由于process这个方法里面没有会致使goroutine切换的条件。而后咱们在for循环里面加入一个操做,例如time.Sleep()或者make分配内存等等协程
1 package main 2 3 import ( 4 "fmt" 5 "sync" 6 "time" 7 ) 8 9 func process(id int) { 10 fmt.Printf("id: %d\n", id) 11 for { 12 time.Sleep(time.Second) 13 } 14 } 15 func main() { 16 var wg sync.WaitGroup 17 n := 10 18 wg.Add(n) 19 for i := 0; i < n; i++ { 20 go process(i) 21 } 22 wg.Wait() 23 }
Output:对象
id: 2
id: 0
id: 1
id: 9
id: 6
id: 3
id: 7
id: 8
id: 5
id: 4blog
能够看到此次的输出就是咱们预料的结果了。在知道goroutine的调度策略以后,能够想到这种策略可能会带来的问题,假若有n个goroutine出现阻塞,而且n >= GOMAXPROCS时,将会致使整个程序阻塞。内存
然而这个问题是没法从根本上解决的,因此go给咱们提供了一个方法runtime.Gosched(),调用这个方法可让当前的goroutine主动让出CPU,这也不失为一个弥补的好方法了。并且在对go程序性能调优的时候,咱们能够根据实际状况来调整GOMAXPROCS的值,例如当有密集的IO操做时,尽可能把这个值设置大一点,能够避免因为大量IO操做致使阻塞线程。编译器
以上内容纯属原创,若有问题欢迎指正!