golang-协程理解

泄露秘密 提交于 2020-02-03 08:05:07

本文总结一下go协程的理解,如有错误望请指正。

网上都说协程是一种轻量级线程,线程又是一种轻量级的进程。这话在语言层面看来是没有错的,但它们的实现是不同的。

线程是cpu资源调度的最小单位。协程不由cpu进行调度,由应用程序进行调度,也就是由go进行调度。在go中,协程的调度也有专门的调度器。但go的协程调度器的复杂程度比cpu的线程调度器是要低许多的。

计算机进程/线程的运行是抢占式的,操作系统负责分配cpu执行时间给各个线程,当时间到达后,当前线程必须暂停进入睡眠,等待后续获得cpu执行时间再度恢复执行;当然线程也可以主动放弃cpu,进入睡眠,这一操作大多由程序员来控制。

协程实现原理

下面说下go中协程的实现原理,这些是我个人所知道的,错误之处望指正。

go中协程的实现原理还是基于线程的,大致由一个数据队列和多个线程实现。将一个个协程的代码放入数据队列,由go内部的线程去数据队列中拉取协程进行执行,协程本质还是运行在线程上的。go明面上并没有提供任何创建启动一个线程的方法,只提供了创建运行协程的方法。当用户创建的协程越来越多,线程处理不完时,go自己就会创建新的线程来执行协程。可以看到go本质上也是多线程的运行方式。但它与传统的一个请求一个线程的方式又有不同,在go中,http服务接受一个请求是创建一个协程来为这个请求服务的,可以看到,在资源利用方法,一个请求一个协程比一个请求一个线程要好很多。这也是go对并发编程友好的原因。

协程的调度

前面说了,go中协程有自己的调度算法,协程由线程进行调度,go中协程的执行不会主动放弃cpu,当一个线程执行一个协程时,如果这个协程没有发生系统调用或者进入睡眠(什么是系统调用文末讲解),那么在这个线程的cpu执行时间内,这个协程是会一直占用这个线程的。其它协程是抢不到这个线程的执行权的。那什么时候协程会放弃cpu进入睡眠呢?就是在发生系统调用和主动进入睡眠时。这个时候这个协程后面的其它协程会由其它线程来进行调度执行,防止因为一个协程发生系统调用和主动进入睡眠时阻塞后面的协程执行。当协程系统调用完毕或睡眠结束后,重新进入数据队列等待其它线程进行调度执行它。

系统调用

在go中发生协程调度时的系统调用指的是各种计算机硬件调用,比如磁盘读取,键盘/鼠标事件读取这些系统IO调用,也就是说在发生IO操作时会发生协程调度,在go中发生网络IO也会发生协程调度,调用go标准库time进行睡眠也会发生协程调度,当channel阻塞的时候也会发生协程调度。

上面说了这么多,下面用一点代码看看效果:

package main

import (
	"fmt"
	"os"
	"runtime"
)

func go1() {
	for {
		fmt.Print(1)
	}
}
func go2() {
	for {
		fmt.Print(2)
		os.Exit(1)
	}
}
func main()  {
	runtime.GOMAXPROCS(2) // 设置最多有两个协程并发执行
	go go1() // 启动一个协程
	go go2() // 启动一个协程
	for{}

}

上面的代码中启动了三个协程,一个主协程也就是main协程和两个普通协程,第一个协程打印1,第二个打印2,同时我们设置了最多两个协程并发执行,其中main肯定占一个,另外一个不是协程1就是协程2,但他俩不会并行执行。另外在协程2执行一次后就退出整个程序,需要注意的是fmt库是会向标准输出的写入内容的,也就是说fmt.Print函数的执行是发生了系统调用的。

上面的代码无论我们执行多少次,程序都会退出。当协程1先执行后,发生系统调用,协程2执行,协程2执行完后程序退出。或者协程2先执行然后退出。但后一种情况很少见(我没有试出来过)。

如果改一下上面的代码:

package main

import (
	"fmt"
	"os"
	"runtime"
)

func go1() {
	for {
		// fmt.Print(1)
	}
}
func go2() {
	for {
		fmt.Print(2)
		os.Exit(1)
	}
}
func main()  {
	runtime.GOMAXPROCS(2) // 设置最多有两个协程并发执行
	go go1() // 启动一个协程
	go go2() // 启动一个协程
	for{}

}

注释掉协程1中的fmt.Print方法,再运行代码,就会看到,程序什么都不会输出,但也不退出,这是因为程序一直在协程1中执行,协程1中没有任何系统调用的代码,协程1一直占用着cpu,设置了最多两个协程,main和协程1各占一个,而且没有发生系统调用,协程2永远得不到调度。

同理,将 runtime.GOMAXPROCS(3) 设置为3,那么执行结果肯定程序会正常退出,原因就不在解释了。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!