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\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: 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\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: 4

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

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

 

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

转载于:https://www.cnblogs.com/xiaxiaosheng/p/11190743.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
上下文切换是指当CPU从一个任务切换到另一个任务时,需要保存当前任务的上下文信息,并加载下一个任务的上下文信息。在Linux系统中,可以使用vmstat命令、pidstat命令、/proc/interrupts文件来查看系统的上下文切换情况。 上下文切换的主要原因有几点: 1. 多任务调度:操作系统需要将CPU资源在多个任务之间进行切换,以实现任务的并发执行。 2. 中断处理:当系统接收到硬件中断信号时,需要暂停当前任务的执行,转而处理中断请求。 3. 用户态与内核态的切换:当用户态进程需要调用内核服务时,会触发上下文切换上下文切换涉及到的资源主要有寄存器、内存、页表等。上下文切换次数过多会导致CPU的资源浪费,降低系统的执行效率。 根据引用的分析,上下文切换共分为三种情况: 1. 自愿上下文切换:当一个任务需要等待某个事件的完成时,会主动释放CPU,从而触发自愿上下文切换。 2. 非自愿上下文切换:当一个任务的时间片用完或发生硬件中断等原因时,会被系统强制调度,触发非自愿上下文切换。 3. 中断上下文切换:当系统接收到硬件中断信号时,会从当前任务切换到中断处理程序,进行中断处理。 至于您提到的Linux上下文切换的实战,可以使用vmstat命令、pidstat命令和/proc/interrupts文件来观察系统的上下文切换情况。具体的使用方法可以参考相关文档或在线资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值