进程、线程、协程?go中的协程原理详解 - 协程是什么?为什么要有协程?

news2024/11/8 19:37:40

1. 为什么要有协程?

我们使用工厂来表示计算机,而内存空间就相当于工厂的地皮,那么我们可以认为进程就是工厂中的厂房,它占据了工厂的地皮。(进程是分配资源的最小单位
在这里插入图片描述
线程是什么呢?我们继续使用这个场景来理解,线程此时就相当于工厂中的流水工作线(线程时资源调度的最小单位),每个厂房可以有多个流水线(进程可以有多个线程),流水线的存在占据了厂房的空间(线程使用系统分配给进程的内存,且同一个线程之间共享内存)。
在这里插入图片描述

我们运行两段代码来模拟线程并发的工作状态,如下图所示:让CPU在多线程之间互相切换,如下图所示,CPU先执行线程一,获得中间结果,然后系统对该CPU进行切换;

在这里插入图片描述

切换后来到线程二,如下图所示,获得线程二的中间结果,循环往复,这样一个单核CPU就实现了多线程的并发工作。
在这里插入图片描述

通过以上的过程,我们可以知道线程会占用CPU时间,且线程的调度需要由系统来进行,开销会比较大,如果还是使用如上的场景来理解,线程相当于工厂的生产线(线程里跑的程序就是生产流程),会占用工人的工时。所以线程的缺点如下:

  • 线程本身占用资源大
  • 线程的操作开销大
  • 线程切换开销大

那么协程是如何工作的呢?首先协程包括执行代码和临时执行的状态(如变量中间值),此时的CPU将固定在一个线程上执行,所以不存在线程的切换开销,通过将协程的相关数据放在线程上,让CPU来执行,执行结束后将中间结果存放至协程,然后清空,转而执行协程二。
在这里插入图片描述
协程二的执行和协程一相似,也是先将其放在线程上进行执行,然后保存相关中间变量。
在这里插入图片描述

这样的方式就是让同一个线程执行了多个协程。协程的本质是将一段数据的运行状态进行打包,可以在线程之间调度,所以协程并不取代线程,协程也在线程上运行(线程是协程的资源,协程使用线程这个资源去运行)。这样的好处,也就是说协程的好处:

  • 资源利用:协程可以利用任何的线程去运行,不需要等待CPU的调度;
  • 快速调度:协程可以快速地调度(避开了系统调用和切换),快速的切换;
  • 超高并发:有限的线程就可以并发很多的协程;

2. 协程的本质

首先可以使用go 函数名()来启动一个协程。在go语言中,协程的本质是一个名为g的结构体,由于该结构体内部成员非常多,我们抓取几个重要的变量来进行说明,具体如下:

在这里插入图片描述

首先最左侧的就是协程的结构体,本文主要关注其中4个结构体变量,第一个变量是一个stack结构体,该结构体中有两个指针,分别指目前栈中数据的高位指针hi和低位指针lo;

第二个变量是sched结构体,其中有一个gobuf结构体,gobuf中存有该协程的目前的运行状态,如sp即是栈指针,指向压栈的某一条数据,其实就是目前运行中的某个函数,初次以外pc即是程序计数器,其中存放的是目前运行到了哪一行代码。

第三个变量atomicstatus,存放的是协程的状态;第四个变量goid,存放的是改协程的id。

3. 协程如何在线程中执行

我们知道协程是用线程去执行的,所以我们观察一下线程的底层,了解线程和协程的关联,在go中线程本质上是一个名为m的结构体,我们同样也只是关注其中几个相关的变量。

在这里插入图片描述

在这里插入图片描述
在go中每个线程都是循环执行一系列工作,又称作单线程循环如下图所示:左侧为栈,右侧位线程执行的函数顺序,其中的业务方法就是协程方法。
在这里插入图片描述
普通协程栈只能记录业务方法的业务信息,且当线程没有获得协程之前是没有普通协程栈的。所以在内存中开辟了一个g0栈,专门用于记录函数调用跳转的信息。下表是执行环境
在这里插入图片描述
以上就成功地在线程上执行了协程,但目前在实际使用中,其实是一种多线程循环,如下图所示:
在这里插入图片描述

在这里插入图片描述
但这种多线程获取一个协程的过程中将会存在并发问题,所以在该过程中需要锁的存在。这种线程循环非常像线程池,操作系统并不知道协程的存在,二是执行一个调度循环来顺序执行协程。

但是这里我们学习的线程循环使得协程只能顺序执行,意思就是在系统中的线程数目确定的情况下,使用这种线程循环只能同时执行与系统中线程数目相等的协程,在某种意义上这其实还是一种顺序执行。且在多线程循环中,线程为了执行协程任务需要从队列中获取协程信息,在这个过程中需要抢锁,这同样也会导致一些问题。

4. G-M-P调度模型

这部分我们主要解决上文中我们提及多线程循环存在的问题,当多个线程来全局获取协程任务时,往往需要抢锁,这就使得可能会出现锁冲突,如下图所示:
在这里插入图片描述
解决方法就其本质就是减少线程在全局环境中尽量减少抢锁的操作,转而在本地无锁的执行协程任务。这种思想的专业术语称为本地队列,就是让线程在抢锁之后一次性抓取多个协程执行,将这些抓取到的协程链接为本地队列,当抓取的所有协程全部执行结束后,才会去全局抢锁,这样就避免了一部分的抢锁操作。
在这里插入图片描述
接下来介绍的G-M-P调度模型就是go中用于解决锁冲突的具体调度模型,其中的中的G指的是协程结构体g,M指的是线程结构体m,P指的是也是一个结构体,其实就是一个本地队列。这个结构体的成员非常复杂,我们主要看和调度模型相关的一部分成员。

在这里插入图片描述
在这里插入图片描述

接下来我们总结一下P的作用:

  • M和G之间的中介,我们可以理解为送料器
  • P持有一些G,使得每次获取G的时候不用从全局找
  • 大大减少了并发冲突的情况

注意:

  1. 如果某个线程的本地队列和全局队列中的都没有协程可以执行的情况下,此时该线程就会去其他线程“窃取”协程,从而增大线程的利用率。
  2. 如果新建协程,系统将会随机寻找一个本地队列,将新建协程置于P的runnext进行插队(在go中认为新建协程的优先级高),如果本地队列都满了,就会将这个新建协程放在全局队列中。

5. 协程并发

我们在前文中将线程循环的抢锁问题使用调度模型解决了,剩下一个关于如何让协程并发的问题,这个问题乍一看我们很容易觉得没有什么问题,但实际上这将会造成协程饥饿问题。这个问题指的是在线程正在执行的某一个协程所需时间过多,使得在队列中的某些时间敏感的协程执行失败。

基本的解决思路是当协程执行一段时间后将当前任务暂定,执行后续协程任务,防止时间敏感携程执行失败。如下图所示:
在这里插入图片描述
当目前线程中执行的协程是一个超长时间的任务,此时先保存该协程的运行状态也就是保护现场,若是后续还需继续执行就将其放入本地队列中去,所示不需要执行就将其处于休眠状态,然后直接跳转到schedule函数中。

这样就让本地队列成了一个小循环,但是如果目前系统中的线程的本地队列中都拥有一个超大的协程任务,那么所有的线程都将在一段时间内处于忙碌状态,全局队列中的任务将会长期无法运行,这个问题又称为全局队列饥饿问题,解决方式就是在本地队列循环时,以一定的概率从全局队列中取出某个任务,让它也参与到本地循环当中去。

这样似乎就很完美了,但是实际上当协程正在运行状态时,我们很难将协程的任务打断,解决方案如下:

  1. 主动挂取:gopark方法,当业务调用这个方法线程就会直接回到schedule函数并切换协程栈,当前运行的协程将会处于等待状态,等待状态的协程是无法立即进入任务队列中的。程序员无法主动调用gopark函数,但是我们可以通过Sleep等具有gopark的函数来进行主动挂取,Sleep五秒之后系统将会把任务的等待状态更改为运行状态放入队列中。
  2. 系统调用完成时:go程序在运行状态中进行了系统调用,那么当系统的底层调用完成后就会调用exitsyscall函数,线程就会停止执行当前协程,将当前协程放入队列中去。
  3. 标记抢占morestack():当函数跳转时都会调用这个方法,它的本意在于检查当前协程栈空间是否有足够内存,如果不够就要扩大该栈空间。当系统监控到协程运行超过10ms,就将g.stackguard0置为0xfffffade(该值是一个抢占标志),让程序在只执行morestack函数时顺便判断一下是否将g中的stackguard置为抢占,如果的确被标记抢占,就回到schedule方法,并将当前协程放回队列中。

6. 基于信号的抢占式调度

当程序在执行过程中既无法主动挂起,越不能进行系统调用,且无法进行函数调用时,也就是说以上关于协程并发的解决方法都行不通时,我们该怎么办?所以提出了基于信号的抢占式调度。这里的信号其实就是线程信号,在操作系统中有很多基于信号的底层通信方式,而我们的线程可以注册对应信号的处理函数。

基本的思路:

  • 注册SIGURG信号(该信号其他地方用的很少)的处理函数
  • GC工作时(GC工作意味着某些线程停了),向目标线程发送信号
  • 线程收到信号,触发调度。

在这里插入图片描述

当GC放信号之后,当前正在处理协程任务的线程将会执行doSigPreempt函数,将当前协程放回队列,重新调用schedule函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/671910.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电商运营个人简历范文

电商运营个人简历篇1 个人信息 某某x 性 别: 男 婚姻状况: 已婚 出生年月: 1981年1月 民 族: 汉族 身 高: 175cm 最高学历: 大专 体 重: 70公斤 计算机能力: 高级程序员 技术职称&am…

MySQL的IF(exp1, exp2, exp3)、IFNULL(exp1, exp2)、ISNULL(exp)函数的用法

本章主要是讲解一下mysql的常用方法if()和ifnull()的使用 1、ISNULL(exp) 该函数的作用是判断表达式exp是否为null,如果是null,则返回1,否则返回0 案例代码:以上两条语句的查询结果为分别为1和0 select ISNULL(null) from dual…

管理类联考——逻辑——技巧篇——形式逻辑——考官出题套路

文章目录 考官出题套路一:代入逻辑推命题真假1. 考官出题套路特征:2. 核心考点: 考官出题套路二:判断逻辑真假1. 考点概述:2. 快速秒杀选项技巧: 考官出题套路三:推矛盾和“两难推理”1. 考点概…

一句话木马(绝对入门,实战环境安装+测试)主要是记录我的第一次一句话木马过程,复现40分钟左右。

首先介绍一下,搭建在本地的靶场,可以做渗透和注入的测试,真是好东西。 安装教程我推荐一篇博客,真的写的很好,我全程坐下来非常流畅非常流畅,csdn少见的高质量博客 DVWA下载安装_PisaYu的博客-CSDN博客 膜…

MFC第五天 Unicode软件开发 MFC框架构成与封装类原理

文章目录 Unicode软件开发以Unicode为字符集的记事本软件开发 MFC框架构成与封装类原理示例代码如下: Unicode软件开发 Unicode软件开发时需要遵循以下规则:使用中可尽量使用自适应版本。 Unicode软件开发: a)微软的软件工程现在默认使用Uni…

【MYSQL篇】mysql中相关锁和MVCC详解

文章目录 前言MVCC1、第一个事务2、第二个事务3、第三个事务4、第四个事务5、第五个事务 InnoDB 常见的几种锁机制共享锁排它锁意向锁记录锁间隙锁临键锁 小结 前言 数据库的锁是在多线程高并发的情况下用来保证数据稳定性和一致性的一种机制。MySQL 根据底层存储引擎的不同&a…

OpenGL

需要继承的两个类 #include <QOpenGLWidget> #include <QOpenGLFunctions_3_3_Core> class OpenGLWidget : public QOpenGLWidget,public QOpenGLFunctions_3_3_Core { Q_OBJECT public: explicit OpenGLWidget(QWidget *parent nullptr); virtual vo…

如何看待调查称半数年轻人存款不足10万?

文章目录 一、目前的存款在哪一个区间&#xff1f;你觉得存款难吗&#xff1f;2.1 自己的状态2.2 对理财的看法和态度 二、谈谈我为存款做出过哪些努力&#xff1f;三、除了个人因素外&#xff0c;有哪些因素影响到了年轻人的存款能力和存款意愿&#xff1f;四、要攒够多少存款…

【数据库原理与实践】CS系的实验期末考题(20222023)

2022&#xff1a; 学校管理数据库涉及四个关联表结构&#xff1a; 学生表 Student(Sno,Sname,Sdate,Ssex) &#xff0c;其中Sno学生编号&#xff0c;Sname学生姓名&#xff0c;Sdate出生年月&#xff0c;Ssex学生性别 。 课程表 Course(Cno,Cname,Tno) &#xff0c;其中Cno课…

(写自己语言的练手级应用)JSON(JavaScript Object Notation) 产生式(BNF)

写自己的开发语言时&#xff0c;很多人都会拿JSON当第一个练习对象 开源net json FJSON 解析工具https://dbrwe.blog.csdn.net/article/details/107611540?spm1001.2014.3001.5502 <json> :: <object> | <array> <object> :: "{" [ <me…

【OJ比赛日历】快周末了,不来一场比赛吗? #06.23-06.29 #13场

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2023-06-23&#xff08;周五&#xff09; #5场比赛2023-06-24…

k8s中如何修改pod中mysql的连接数

(方法一) 临时更改设置最大连接数据&#xff08;建议先临时修改&#xff0c;项目没有问题之后再进行永久修改&#xff09; 使用Navicat连接上数据库&#xff0c;点击连接名——点击新建查询—— 查看最大连接数 show variables like ‘’%max_connections%‘’; 查看当前用户使…

vue中注册组件的两种方式(全局注册 局部注册)

vue 是一个完全支持组件化开发的框架&#xff0c; 组件之间可以进行相互的引用。vue 中组件的引用原则&#xff1a;先注册后使用。 1. 组件的注册 组件之间可以进行相互的引用&#xff0c;例如&#xff1a; 注册组件的的方式&#xff1a;分为“全局注册”和“局部注册”两种…

Git Bash 上传本地文件到Gitee(AI助力解决问题)

前言 消失了将近一个月&#xff0c;预祝大家端午节快乐&#xff01; 这篇文章主要介绍下在上传本地项目到gitee时出现的问题&#xff0c;以及借助AI解决问题。 Gitee是一个基于 Git 的代码托管和开发协作平台&#xff0c;它提供了代码仓库、代码审查、持续集成/持续部署 (CI/C…

开关电源——DCDC变换器设计

开关电源——DCDC变换器设计 对于DCDC变换器来说&#xff0c;最重要的部分也是唯一一个磁学元件——电感需要考虑。 直流传递函数 正如之前我们所说&#xff0c;电感电流在开关导通的时候增加的电流必须等于在开关关断时候的减少的电流&#xff0c;即在一个工作周期中不积累…

支付宝性能测试案例分析

双11过程当中&#xff0c;促销开启的第一分钟内支付宝的交易总额就突破了一亿元&#xff0c;短时间内大量用户涌入的情况下&#xff0c;如何保证用户的支付顺畅&#xff0c;是对支付宝应用系统的一个极大的挑战。 一、性能测试支付宝场景介绍 双11过程当中&#xff0c;促销开启…

企业级ChatGPT开发的三大核心内幕及案例实战(三)

企业级ChatGPT开发的三大核心内幕及案例实战(三) 2.3 Notion 问答对话AI案例演示及源码分析 Gavin老师:NLP_Matrix_Space 如图2-2所示,我们先看一下Notion 问答对话AI案例的效果。你问一个问题,它会进行回答,然后它会告诉你,信息来源在什么地方,要看具体的信息,可以…

ICLR 2023 | RevCol:给神经网络架构增加了一个维度!大模型架构设计新范式

点击蓝字 关注我们 关注并星标 从此不迷路 计算机视觉研究院 公众号ID&#xff5c;计算机视觉研究院 学习群&#xff5c;扫码在主页获取加入方式 论文地址&#xff1a;https://arxiv.org/pdf/2212.11696.pdf 项目代码&#xff1a;https://github.com/megvii-research/RevCol 计…

MySQL进阶SQL语句

目录 1.select&#xff08;显示表格中一个或数个字段的所有数据记录&#xff09; 2.distinct&#xff08;不显示重复的数据记录&#xff09; 3.where&#xff08;有条件查询&#xff09; 4.and 、or&#xff08;且、或&#xff09; 5. in&#xff08;显示已知的值的数据记…