协程没有秘密(一):原来这么简单易懂,拿走

大家好呀,我是码财同行。今天咱们继续聊聊协程。

关于协程的文章网上有很多,但是大部分看完都是云里雾里的,要么摆理论,要么直接上代码,实际的效果并没有那么好。

我自己有很多年协程的使用经验,有C++,也有Golang的,算是对协程有一定的了解。在本文中,我将以自己的理解帮大家系统梳理一下协程的工作机制,力求做到简单易懂。

话不多说,直接开始~

程序执行的本质

首先,在正式学习协程之前,咱们要理解程序执行的本质。毕竟,协程相关的知识还是没有脱离程序执行的基本要素。就好比,咱们在学跑之前,先把走路学会了。

从宏观上来说,程序执行就是计算机硬件执行程序代码,完成特定任务的过程。在这个过程中,可以有特定的输入参数,而输出就是任务完成后的结果。

那具体又是如何执行的呢?我们可以将上图中的元素进一步分解和转换。

计算机硬件可以简略的分成执行指令的 CPU 和存储数据的内存:

程序代码中的代码片段可以被翻译成 CPU 可以理解的指令;输入参数和程序代码中的一些数据可以组成程序执行时的数据:

此时,从微观上来说,程序执行就是CPU执行指令,读写内存中的数据的过程。

程序的执行流

好,现在我们再来理解一下程序执行流的概念。

不同类型的执行流

前面我们刚刚讲到了一个简单程序的执行是这样的:

对于计算机硬件来说,面对的任务(程序代码)可以有很多个的,毕竟一台计算机上肯定有很多程序需要执行嘛~

这样,对应关系就变成了下面这样,计算机硬件需要在多份程序代码中切换,一会执行A,一会执行B,一会执行C ...

这就是所谓的 CPU 分时调用,就是因为计算机硬件中 CPU 在任一时刻是需要被程序独占的,而内存可以被多个程序共享,大家都把数据存在内存中。

随着计算机技术的发展,有了线程机制。一个程序中的代码片段可以通过线程做分工,例如,一个线程负责网络,一个线程负责逻辑任务,他们互不干扰。

在这里,我们把分工后的代码片段看成一个个独立的执行流,有两个线程就对应两个执行流。

再进一步,有人又提出了协程的机制。协程,本质上就是对任务(程序代码)逻辑上的进一步拆分:只要你认为程序中相对独立的代码片段都可以构成一个个协程,它相对于线程分工来说,拆分的粒度更细。

为了方便大家理解,我这里举一个例子。比如,玩家在玩一个游戏中的公会玩法,玩家操作完成后,程序需要做对玩家执行一些逻辑(比如加一些金币),然后还需要执行公会相关的逻辑(比如增加玩家所在公会的经验),还要给玩家发送一封奖励邮件等等。

上面任务逻辑的多个部分其实是相互独立的,可以作为一个个独立的执行流单独执行,而且互不干扰。

我们就可以把这多个代码片段放到一个个协程中执行。用 golang 的伪代码表示就是下面这样:

golang 复制代码
func HandleLogic() {
    //开始执行工会玩法逻辑
    
    //增加玩家金币
    AddPlayerCoin()

    //开启协程,执行工会逻辑
    go func() {
        //增加工会经验
        AddGuildExp()
    }()
    
    //开启协程,发送邮件
    go func() {
        SendPlayerMail()
    }()
    
    // 其他逻辑
}

现在,程序代码的执行变成了下面这样多样化的情况:

不管是单独一个程序,还是包含线程、协程,每个代码片段都对应的是一个个单独的执行流

同样的,计算机硬件需要在多个单独的执行流中切换。这种计算机需要处理多个执行流的情况称为 并发

执行流的切换

程序的逻辑可以被拆分成多个执行流/代码片段。CPU 在并发执行多个执行流的时候,需要按照一定规则依次切换执行,最大限度的 保证公平,使得每个执行流从时间轴上来看都是持续执行的。

如果是单 CPU 硬件,就需要将时间切分成时间片,一会执行这个,一会执行那个。

如果硬件有多核 CPU,则执行也是类似的,多个执行流可能被分配在多个 CPU 上执行。这个时候多个执行流才可能 并行 执行的。

那对于特定一个 CPU 来说,在该 CPU 上执行的多个执行流是有一个切换的过程。我们都学过,对于进程和线程执行流来说,这个切换是由操作系统负责的,操作系统保存好前一个执行流的上下文之后,通过调度算法选择下一个执行流,就可以执行下一个执行流了,细节这里不讨论。

我们要重点说明的是协程的切换过程,但基本原理是类似的,我们详细说一下。

下面是添加了切换流程的协程执行过程:

但具体要怎么切换呢?

这里就涉及程序执行的微观细节。

概括来说涉及以下几个方面:

  • 上下文到底是啥?执行的代码切走指什么?
  • 调度:下一个待执行的协程怎么选?
  • 上下文处理:寄存器与栈的处理

详细的,我们下一篇文章来谈,敬请期待。

好了,看了这么多,一定很费脑力吧。来个笑话放松一下 :)

【笑话一则】喝多了准备打车回家,本来想叫嘀嘀打车,结果叫成了滴滴代驾。等代驾到了,我们对视了10秒,异口同声得说:"你车呢?"

最后,请大家来个点赞、收藏、转发吧,您的鼓励是我持续创作的动力,蟹蟹!

相关推荐
NiNg_1_2343 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
Chrikk5 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*5 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue5 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man5 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
customer086 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
Yaml47 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
测试19988 小时前
2024软件测试面试热点问题
自动化测试·软件测试·python·测试工具·面试·职场和发展·压力测试