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

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

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

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

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

相关推荐
逸风尊者12 分钟前
开发也能看懂的大模型:RNN
java·后端·算法
奇妙之二进制1 小时前
2025年春招修订版《C/C++笔面试系列》(1) C语言经典笔面试题(上)
c语言·c++·面试
小钟不想敲代码2 小时前
第4章 Spring Boot自动配置
java·spring boot·后端
hummhumm2 小时前
第33章 - Go语言 云原生开发
java·开发语言·后端·python·sql·云原生·golang
兑生2 小时前
力扣面试150 填充每个节点的下一个右侧节点指针 II BFS 逐层构建法
leetcode·面试·宽度优先
AskHarries2 小时前
利用 OSHI获取机器的硬件信息
java·后端
凡人的AI工具箱3 小时前
40分钟学 Go 语言高并发:【实战】并发安全的配置管理器(功能扩展)
开发语言·后端·安全·架构·golang
我的运维人生3 小时前
Spring Boot应用开发实战:构建RESTful API服务
spring boot·后端·restful·运维开发·技术共享
颜淡慕潇3 小时前
【K8S系列】深入解析 Kubernetes 中的 Deployment
后端·云原生·容器·kubernetes
Iced_Sheep4 小时前
Spring @Transactional 你真的会用吗???
后端·spring