来源 | 后端技术指南针(ID:gh_ed1e2b37dcb6)

Go语言的巨大潜力有目共睹,今天我们来学习Go语言的Goroutine机制,这也可能是Go语言最为吸引人的特性了,理解它对于掌握Go语言大有裨益,话不多说开始吧!

通过本文你将了解到以下内容:

  • 什么是协程以及横向对比优势

  • Go语言的Goroutine机制底层原理和特点

聊聊协程

大家对于进程、线程二位明星都很熟悉,但协程就没有火了,是协程不是携程哦!

协程并不是Go语言特有的机制,相反像Lua、Ruby、Python、Kotlin、C/C++等也都有协程的支持,区别在于有的是从语言层面支持、有的通过插件类库支持。Go语言是原生语言层面支持,本文也是从Go角度去理解协程。

1.1 协程基本概念和提出者

协程英文是Coroutine译为协同程序,我们来看下维基百科对Coroutine的介绍:

Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed.

Coroutines are well-suited for implementing familiar program components such as cooperative tasks, exceptions, event loops, iterators, infinite lists and pipes.

According to Donald Knuth, Melvin Conway coined the term coroutine in 1958 when he applied it to construction of an assembly program.The first published explanation of the coroutine appeared later, in 1963.

简单翻译一下:

协同程序是一种计算机程序组件,它允许暂停和恢复执行,从而可以作为通用化的非抢占式多任务处理子程序。

协同程序非常适合实现例如协作任务、异常、事件循环、迭代器、管道等熟悉的程序组件。

根据唐纳德·克努特的说法,梅尔文·康威在1958年将Coroutine这个术语应用于装配程序的构建,直到在1963年才首次发表了阐述Coroutine的论文。

协程的提出者梅尔文·爱德华·康威是一位计算机科学家,除了协程之外他还创造了Conway's Law康威定律,他基于社会学观察提出了系统设计的一些观点,本文就不展开了,感兴趣的可以看下作者的论文How Do Committees Invent?:

http://www.melconway.com/Home/Committees_Paper.html

1.2 协程和进线程的对比

我们来复习一下进线程和协程的一些基本特点吧:

进程是系统资源分配的最小单位, 进程包括文本段text region、数据段data region和堆栈段stack region等。进程的创建和销毁都是系统资源级别的,因此是一种比较昂贵的操作,进程是抢占式调度其有三个状态:等待态、就绪态、运行态。进程之间是相互隔离的,它们各自拥有自己的系统资源, 更加安全但是也存在进程间通信不便的问题。

进程是线程的载体容器,多个线程除了共享进程的资源还拥有自己的一少部分独立的资源,因此相比进程而言更加轻量,进程内的多个线程间的通信比进程容易,但是也同样带来了同步和互斥的问题和线程安全问题,尽管如此多线程编程仍然是当前服务端编程的主流,线程也是CPU调度的最小单位,多线程运行时就存在线程切换问题,其状态转移如图:

协程在有的资料中称为微线程或者用户态轻量级线程,协程调度不需要内核参与而是完全由用户态程序来决定,因此协程对于系统而言是无感知的。协程由用户态控制就不存在抢占式调度那样强制的CPU控制权切换到其他进线程,多个协程进行协作式调度,协程自己主动把控制权转让出去之后,其他协程才能被执行到,这样就避免了系统切换开销提高了CPU的使用效率。

抢占式调度和协作式调度的简单对比:

看到这里我们不免去想:看着协作式调度优点更多,那么为什么一直是抢占式调度占上风呢?让我们继续一起学习,可能就能解答这个问题了。

1.3 实际工作中的我们

我们写程序的时经常需要考虑的因素就是提高机器使用率,这个非常好理解。当然机器使用率和开发效率维护成本往往存在权衡,说句大白话就是:要么费人力要么费机器,选一个吧!

机器成本里面最贵的就是CPU了,程序一般分为CPU密集型和IO密集型,对于CPU密集型我们的优化空间可能没那么多,但对于IO密集型却有非常大的优化空间,试想我们的程序总是处于IO等待中让CPU呼呼睡大觉,那该多糟糕。

为了提高IO密集型程序的CPU使用率,我们尝试多进程/多线程编程等让多个任务一起跑分时复用抢占式调度,这样提高了CPU的利用率,但由于多个进线程存在调度切换,这也有一定的资源消耗,因此进线程数量不可能无限增大。

我们现在写的程序大部分都是同步IO的,效率还不够高,因此出现了一些异步IO框架,但是异步框架的编程难度比同步框架要大,但不可否认异步是一个很好的优化方向,先不要晕,来看下同步IO和异步IO就知道了:

同步是指应用程序发起I/O请求后需要等待或者轮询内核I/O操作完成后才能继续执行,异步是指应用程序发起I/O请求后仍继续执行,当内核I/O操作完成后会通知应用程序或者调用应用程序注册的回调函数。

我们以C/C++开发的服务端程序为例,Linux的异步IO出现的比较晚,因此像epoll之类的IO复用技术仍然有相当大的地盘,但是同步IO的效率毕竟不如异步IO,因此当前的优化方向包括:异步IO框架(像boost.asio框架)和协程方案(腾讯libco)。

Go和协程

我们知道协程是Coroutine,Go语言在语言层面对协程进行了原生支持并且称之为Goroutine,这也是Go语言强大并发能力的重要支撑,Go的CSP并发模型是通过Goroutine和channel来实现的,后续会专门写一下CSP并发模型。

2.1 协作式调度和调度器

协作式调度中用户态协程会主动让出CPU控制权来让其他协程使用,确实提高了CPU的使用率,但是不由得去思考用户态协程不够智能怎么办?不知道何时让出控制权也不知道何时恢复执行。

读到这里忽然明白了抢占式调度的优势了,在抢占式调度中都是由系统内核来完成的,用户态不需要参与,并且内核参与使得平台移植好,说到底还是各有千秋啊!

为了解决这个问题我们需要一个中间层来调度这些协程,这样才能让用户态的成千上万个协程稳定有序地跑起来,我们姑且把这个中间层称为用户态协程调度器吧!

2.2 Goroutine和Go的调度器模型

Go语言从2007年底开发直到今天已经发展了12年,Go的调度器也不是一蹴而就的,在最初的几个版本中Go的调度器也非常简陋,无法支撑大并发。

经过多个版本的迭代和优化,目前已经有很优异的性能了,不过我们还是来回顾一下Go调度器的发展历程(详见参考一):


Go的调度器非常复杂,篇幅所限本文只提一些基本的概念和原理,后续会深入去展开Go的调度器。

最近几个版本的Go调度器采用GPM模型,其中有几个概念先看下:


GPM模型使用一种M:N的调度器来调度任意数量的协程运行于任意数量的系统线程中,从而保证了上下文切换的速度并且利用多核,但是增加了调度器的复杂度。

来看两张图来进一步理解一下:



整个GPM调度的简单过程如下:

新创建的Goroutine会先存放在Global全局队列中,等待Go调度器进行调度,随后Goroutine被分配给其中的一个逻辑处理器P,并放到这个逻辑处理器对应的Local本地运行队列中,最终等待被逻辑处理器P执行即可。

在M与P绑定后,M会不断从P的Local队列中无锁地取出G,并切换到G的堆栈执行,当P的Local队列中没有G时,再从Global队列中获取一个G,当Global队列中也没有待运行的G时,则尝试从其它的P窃取部分G来执行相当于P之间的负载均衡。

Goroutine在整个生存期也存在不同的状态切换,主要的有以下几种状态:

画个状态图看下:


巨人的肩膀

  • https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-goroutine/

  • https://www.flysnow.org/2017/04/11/go-in-action-go-goroutine.html

  • https://segmentfault.com/a/1190000018150987

  • https://tiancaiamao.gitbooks.io/go-internals/content/zh/05.2.html

  • https://wudaijun.com/2018/01/go-scheduler/

  • https://zhuanlan.zhihu.com/p/77620605

【End】

推荐阅读 

☞钉钉辟谣“老师能打开学生摄像头”;HTC 关闭官方社区;Node.js 安全版本发布 | 极客头条

☞AI 医生“战疫”在前线

☞2020年AI如何走?Jeff Dean和其他四位“大神”已做预测!

☞远程办公众生相:“云”吃饭、被窝打卡、梳妆台编程......

☞2020 年,云游戏将爆发?各大科技公司云游戏布局大曝光!

☞SIM卡交换攻击盗币猖獗,比特币从业者如何自保?

你点的每一个在看,我认真当成了喜欢

Go语言潜力有目共睹,但它的Goroutine机制底层原理你了解吗?相关推荐

  1. golang goroutine实现_Go语言潜力有目共睹,但它的Goroutine机制底层原理你了解吗?...

    来源 | 后端技术指南针(ID:gh_ed1e2b37dcb6) Go语言的巨大潜力有目共睹,今天我们来学习Go语言的Goroutine机制,这也可能是Go语言最为吸引人的特性了,理解它对于掌握Go语 ...

  2. 『Go 语言底层原理剖析』文末送书

    互联网迅猛发展的数十年时间里,不断面领着各种新的场景与挑战,例如大数据.大规模集群计算.更复杂的网络环境.多核处理器引起对于高并发的需求,云计算,上千万行的服务器代码-- 那些成熟但上了年纪的语言没能 ...

  3. 【Go语言学习】——go 数据结构底层原理

    go 数据结构底层原理 array底层原理 go中的数组是由固定长度的特定类型元素组成的序列,数组的长度是数据类型的组成方式,所以不同长度和不同类型的元素组成的数组是不同的数组类型.数组属于值类型,因 ...

  4. Go语言底层原理剖析

    作者:郑建勋 出版社:电子工业出版社 品牌:博文视点 出版时间:2021-08-01 Go语言底层原理剖析

  5. Algorithm:C++语言实现之SimHash和倒排索引算法相关(抽屉原理、倒排索、建立查找树、处理Hash冲突、Hash查找)

    Algorithm:C++语言实现之SimHash和倒排索引算法相关(抽屉原理.倒排索.建立查找树.处理Hash冲突.Hash查找) 目录 一.SimHash算法 1.SimHash算法五个步骤 2. ...

  6. stc15系列c语言pwm编程,STC单片机C语言程序设计 第25章 STC单片机增强型PWM原理及实现.docx...

    STC单片机C语言程序设计 第25章 STC单片机增强型PWM原理及实现 STC单片机增强型PWM发生器原理及应用主 讲:何宾Email:hebin@mail.buct.edu.cn2016.03增强 ...

  7. C语言中直接函数调用和间接函数调用的底层调用方式 X86 AArch64

    总结一下X86和AArch64架构下C语言中直接函数调用和间接函数调用的底层调用方式.主要是想记录一下在机器码这个层面直接函数调用和间接函数调用如何获取目标函数的地址. 1.直接函数调用 话不多说,先 ...

  8. 《Go 语言程序设计》读书笔记 (七) Goroutine 与系统线程的区别

    goroutine和线程的区别 动态栈 每一个OS线程都有一个固定大小的内存块(一般会是2MB)来做栈,这个栈会用来存储当前正在被调用或挂起(指在调用其它函数时)的函数的内部变量.这个固定大小的栈同时 ...

  9. golang goroutine 协程原理

    一.goroutine简介 goroutine是go语言中最为NB的设计,也是其魅力所在,goroutine的本质是协程,是实现并行计算的核心.goroutine使用方式非常的简单,只需使用go关键字 ...

最新文章

  1. 人工智能:主导下一轮科技创新红利
  2. IE 中释放javascript对象
  3. 京东SSO单点登陆实现分析
  4. cassandra使用心得_避免在Cassandra中使用清单
  5. JBuilder安装JDBC drive的方法略解
  6. Boostnote跨平台 Markdown 编辑器
  7. jsp主板 jtp_HPlaserjtp1008不开机故障的检测与维修流程
  8. Shiro-单点登录原理
  9. 【数据结构与算法】详解什么是哈希表,并用代码手动实现一个哈希表
  10. 提高前端生产效率(PxCook工具推荐)
  11. MongoDB实验练习题
  12. 单片机复位电路的简谈
  13. 顺丰云服务器,基于华为云云原生解决方案,顺丰“快递+”这一项业务效率提升了48倍...
  14. php 商户转账到微信零钱
  15. C# 源码 ,使用<switch语句>实现学生成绩评测.
  16. 文献阅读(2):王昊奋:大规模知识图谱技术
  17. iOS动画之转场动画CATransition
  18. 纯净Windows镜像安装方法(无网卡和usb驱动)
  19. DB2数据库编目(catalog)概念的理解
  20. 亚类划分数学建模(亚太数学建模思路持续分享)

热门文章

  1. python3--匿名函数
  2. ACM-线段树区间更新+离散化
  3. C语言中全局变量、局部变量、静态全局变量、静态局部变量的区别 (转)
  4. 关于Java中的GUI事件处理
  5. java类加载全过程
  6. 《TensorFlow 2.0深度学习算法实战教材》学习笔记(三、TensorFlow 基础)
  7. fseek linux 大文件_上传大文件
  8. keras sklearn下两分类/多分类的技术杂谈(交叉验证和评价指标)
  9. android堆叠效果相册_CSS实现照片堆叠效果
  10. Linux Shell编程笔记6 sed流编辑器