Go goroutine机理

1.协程

   协程,也称为轻量级线程,具备以下几个特点:

   ·能够在单一的系统线程中模拟多个任务的并发执行;

   ·在一个特定的时间,只有一个任务在运行,即并非真正地并行;

   ·被动的任务调度方式,即任务没有主动抢占时间片的说法。当一个任务正在执行时,外部没有办法中止它。要进行任务切换,只能通过由该任务自身调用yield()来主动出让CPU使用权;

   ·每个协程都有自己的堆栈和局部变量;

    每个协程都包含3种运行状态:挂起、运行和停止。停止通常表示该协程已经执行完成(包括遇到问题后明确退出执行的情况),挂起则表示该协程尚未执行完成,但出让了时间片,以后有机会时会由调度器继续执行。

 

2.协程库(libtask)

   libtask库的下载地址是:http://swtch.com/libtask/,这个库的作者使用的是非常开放的授权协议,因此可以随意修改和使用这些代码,但是必须保持该份代码所附带的版权声明。

   这个libtask库实现了以下几个关键模块:

   ·任务及任务管理

   ·任务调度器

   ·异步IO

   ·channel

   这个静态库直接提供了一个main()入口函数作为协程的驱动,因此库的使用者只需按该库约定的规则实现任务函数taskmain(),启动后这些任务自然会被以协程的方式创建和调度执行。taskmain()函数的声明如下:

   先来看一下简单的C程序例子:

Go goroutine机理_第1张图片

Go goroutine机理_第2张图片  

    该程序从命令行得到一个整型数作为质数的查找范围,比如用户输入了100,则该程序会列出0到100之间的所有质数。

    将以上代码翻译成Go语言代码,如下:

Go goroutine机理_第3张图片

Go goroutine机理_第4张图片

Go goroutine机理_第5张图片

 

3.任务

    从上面的例子可以看出,在实现了一个任务函数后,真要让这个函数加入到调度队列中,我们还需要显式调用taskcreate()函数。下面我们大致介绍一下任务的概念,以及taskcreate()到底做了哪些事情。

       任务用以下的结构表达:

Go goroutine机理_第6张图片

    可以看到,每一个任务需要保存以下这几个关键数据:

    ·任务上下文,用于在切换任务时保持当前任务的运行环境;

    ·栈

    ·状态

    ·该任务所对应的业务函数

    ·任务的调用参数

    ·之前和之后的任务

   下面我们再来看一下任务的创建过程:

Go goroutine机理_第7张图片

Go goroutine机理_第8张图片

Go goroutine机理_第9张图片

   可以看到,这个过程其实就是创建并设置了一个Task对象,然后将该对象添加到alltask列表中,接着将该Task对象的状态设置为就绪,表示该任务可以接受调度器的调度。

 

4.任务调度

   调度器的代码实现如下:

Go goroutine机理_第10张图片

Go goroutine机理_第11张图片

    逻辑其实很简单,就是循环执行正在等待中的任务,直到执行完所有的任务后退出。读者可能会觉得奇怪,这个函数里根本没有调用任务所对应的业务函数的代码,那么那些代码到底是怎么执行的呢?最关键的是下面这一句调用:

 

5.上下文切换

   taskstart()函数的具体实现代码:

Go goroutine机理_第12张图片

    我们知道,在任务执行过程中发生任务切换只会因为以下原因之一:

    ·该任务的业务代码主要要求切换,即主动让出执行权;

    ·发生了IO,导致执行阻塞。

 

    主动出让执行权通过主动调用taskyield()来完成。在下面的代码中,taskswitch()切换上下文以具体做到任务切换,taskready()函数将一个具体的设置为等待执行状态,tasksyield()则借助其他的函数完成执行权出让:

Go goroutine机理_第13张图片

    上面的代码做了这几件事情:

    ·将正在执行的任务放回到等待队列中,免得永远无法再切换回来;

    ·将该任务的状态设置为yield;

    ·进行任务切换

 

    libtask库中的fd.c进行了基于轮询的异步IO封装,并在tcpproxy.c中示范了如何使用异步IO来达成自动出让执行权的效果。

Go goroutine机理_第14张图片

Go goroutine机理_第15张图片

     当发生IO事件时,程序会先让其他处于yield状态的任务先执行,待清理掉这些可以执行的任务后,开始调用poll来监听所有处于IO阻塞状态的pollfd,一旦有某些pollfd成功读写,则将对应的任务切换为可调度状态。

 

6.通信机制

   我们知道,channel是推荐的goroutine之间的通信方式。而实际上,“通信”这个术语并不太适用。从根本上来说,channel只是一个数据结构,可以被写入数据,也可以被读取数据。所谓的发送数据到channel,或者从channel读取数据,说白了就是对一个数据结构的操作,仅此而已。

   下面我们就来看看channel的数据结构:

Go goroutine机理_第16张图片   

Go goroutine机理_第17张图片

    可以看到channel的基本组成如下:

    ·内存缓存,用于存放元素

    ·发送队列

    ·接受队列

    从以下这个channel的创建函数可以看出,分配的内存缓存就紧跟在这个channel结构之后:

Go goroutine机理_第18张图片

 

参考:

https://www.yuque.com/docs/share/8fcfa63e-0748-4bfe-99c5-063f136c6840

你可能感兴趣的:(Go)