zoukankan      html  css  js  c++  java
  • goroutine上下文切换机制

      goroutine是go语言的协程,go语言在语言和编译器层面提供对协程的支持。goroutine跟线程一个很大区别就是线程是操作系统的对象,而goroutine是应用层实现的线程。goroutine实际上是运行在线程池上的,由go的runtime实现调度,goroutine调度时,由于不需要像线程一样涉及到系统调用,要进行用户态和内核态的切换,因此,goroutine被称为轻量级的线程,开销要比线程小很多。然而,这里我想到了一个问题,线程是由操作系统进行调度的,操作系统有对处理器的调度权限,因此线程在上下文切换时,操作系统可以从正在占用处理器的线程手中剥夺处理器的使用权,然而goroutine该怎么完成这个操作呢?

      然而goroutine并不能像线程的调度那样,goroutine调度时,必须由当前正在占用CPU的goroutine主动让出CPU给新的goroutine,才能完成切换操作。

      具体实现是这样的,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
    ", 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: 0

      按照正常的逻辑,这段代码应该会输出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
    ", 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: 4

      可以看到这次的输出就是我们预料的结果了。在知道goroutine的调度策略之后,可以想到这种策略可能会带来的问题,假如有n个goroutine出现阻塞,并且n >= GOMAXPROCS时,将会导致整个程序阻塞。

      然而这个问题是无法从根本上解决的,所以go给我们提供了一个方法runtime.Gosched(),调用这个方法可以让当前的goroutine主动让出CPU,这也不失为一个弥补的好方法了。而且在对go程序性能调优的时候,我们可以根据实际情况来调整GOMAXPROCS的值,例如当有密集的IO操作时,尽量把这个值设置大一点,可以避免由于大量IO操作导致阻塞线程。

    以上内容纯属原创,如有问题欢迎指正!

  • 相关阅读:
    单例类
    日期类2
    日历类
    日期转换类
    抓取网页内容并截图
    关于计时器与多线程
    让页面上图片不变形
    Thread 调用方法的方式
    语音放大缩小
    阻止Enter键回发到服务端Asp.net
  • 原文地址:https://www.cnblogs.com/xiaxiaosheng/p/11190743.html
Copyright © 2011-2022 走看看