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

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

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

我自己有很多年协程的使用经验,有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秒,异口同声得说:"你车呢?"

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

相关推荐
+VX:Fegn089517 分钟前
计算机毕业设计|基于springboot + vue在线音乐播放系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
code bean39 分钟前
Flask图片服务在不同网络接口下的路径解析问题及解决方案
后端·python·flask
+VX:Fegn08951 小时前
计算机毕业设计|基于springboot + vue律师咨询系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
努力的小郑1 小时前
2025年度总结:当我在 Cursor 里敲下 Tab 的那一刻,我知道时代变了
前端·后端·ai编程
颜淡慕潇2 小时前
深度解析官方 Spring Boot 稳定版本及 JDK 配套策略
java·后端·架构
Victor3562 小时前
Hibernate(28)Hibernate的级联操作是什么?
后端
Victor3563 小时前
Hibernate(27)Hibernate的查询策略是什么?
后端
飞鸟真人3 小时前
Redis面试常见问题详解
数据库·redis·面试
superman超哥3 小时前
Rust 内部可变性模式:突破借用规则的受控机制
开发语言·后端·rust·rust内部可变性·借用规则·受控机制
C雨后彩虹4 小时前
计算误码率
java·数据结构·算法·华为·面试