深入Go goroutine理解
IT行业相对于一般传统行业,发展更新速度更快,一旦停止了学习,很快就会被行业所淘汰。所以我们需要踏踏实实的不断学习,精进自己的技术,尤其是初学者。今天golang学习网给大家整理了《深入Go goroutine理解》,聊聊goroutine,我们一起来看看吧!
Go语言最大的特色就是从语言层面支持并发(Goroutine),Goroutine是Go中最基本的执行单元。事实上每一个Go程序至少有一个Goroutine:主Goroutine。当程序启动时,它会自动创建。
为了更好理解Goroutine,现讲一下线程和协程的概念
线程(Thread):有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程的切换一般也由操作系统调度。
协程(coroutine):又称微线程与子例程(或者称为函数)一样,协程(coroutine)也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。
和线程类似,共享堆,不共享栈,协程的切换一般由程序员在代码中显式控制。它避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂。
Goroutine和其他语言的协程(coroutine)在使用方式上类似,但从字面意义上来看不同(一个是Goroutine,一个是coroutine),再就是协程是一种协作任务控制机制,在最简单的意义上,协程不是并发的,而Goroutine支持并发的。因此Goroutine可以理解为一种Go语言的协程。同时它可以运行在一个或多个线程上。
先给个简单实例
func loop() { for i := 0; i <p style="text-align: left"><span style="color: #ff0000"><strong>GO并发的实现原理 </strong></span></p> <p><strong>一、Go并发模型</strong></p> <p>Go实现了两种并发形式。第一种是大家普遍认知的:多线程共享内存。其实就是Java或者C++等语言中的多线程开发。另外一种是Go语言特有的,也是Go语言推荐的:CSP(communicating sequential processes)并发模型。</p> <p>CSP并发模型是在1970年左右提出的概念,属于比较新的概念,不同于传统的多线程通过共享内存来通信,CSP讲究的是“以通信的方式来共享内存”。</p> <p>请记住下面这句话:<br> DO NOT COMMUNICATE BY SHARING MEMORY; INSTEAD, SHARE MEMORY BY COMMUNICATING.<br> “不要以共享内存的方式来通信,相反,要通过通信来共享内存。”</p> <p>普通的线程并发模型,就是像Java、C++、或者Python,他们线程间通信都是通过共享内存的方式来进行的。非常典型的方式就是,在访问共享数据(例如数组、Map、或者某个结构体或对象)的时候,通过锁来访问,因此,在很多时候,衍生出一种方便操作的数据结构,叫做“线程安全的数据结构”。例如Java提供的包”java.util.concurrent”中的数据结构。Go中也实现了传统的线程并发模型。</p> <p>Go的CSP并发模型,是通过<code>goroutine</code>和<code>channel</code>来实现的。</p>
goroutine
是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的”线程“类似,可以理解为”线程“channel
是Go语言中各个并发结构体(goroutine
)之前的通信机制。 通俗的讲,就是各个goroutine
之间通信的”管道“,有点类似于Linux中的管道。
生成一个goroutine
的方式非常的简单:Go一下,就生成了。
go f();
通信机制channel
也很方便,传数据用channel ,取数据用
。
在通信过程中,传数据channel 和取数据
必然会成对出现,因为这边传,那边取,两个
goroutine
之间才会实现通信。
而且不管传还是取,必阻塞,直到另外的goroutine
传或者取为止。
示例如下:
package main import "fmt" func main() { messages := make(chan string) go func() { messages <p>注意 main()本身也是运行了一个goroutine。</p> <p>messages:= make(chan int) 这样就声明了一个阻塞式的无缓冲的通道</p> <p>chan 是关键字 代表我要创建一个通道</p> <p style="text-align: left"><span style="color: #ff0000"><strong>GO并发模型的实现原理</strong></span></p> <p>我们先从线程讲起,无论语言层面何种并发模型,到了操作系统层面,一定是以线程的形态存在的。而操作系统根据资源访问权限的不同,体系架构可分为用户空间和内核空间;内核空间主要操作访问CPU资源、I/O资源、内存资源等硬件资源,为上层应用程序提供最基本的基础资源,用户空间呢就是上层应用程序的固定活动空间,用户空间不可以直接访问资源,必须通过“系统调用”、“库函数”或“Shell脚本”来调用内核空间提供的资源。</p> <p>我们现在的计算机语言,可以狭义的认为是一种“软件”,它们中所谓的“线程”,往往是用户态的线程,和操作系统本身内核态的线程(简称KSE),还是有区别的。</p> <p>线程模型的实现,可以分为以下几种方式:</p> <p><span style="color: #ff0000"><strong>用户级线程模型 </strong></span></p> <p style="text-align: center"><img title="" alt="" src="/uploads/20221230/167239082663aea8aa3f000.jpg"></p> <p>如图所示,多个用户态的线程对应着一个内核线程,程序线程的创建、终止、切换或者同步等线程工作必须自身来完成。它可以做快速的上下文切换。缺点是不能有效利用多核CPU。</p> <p><span style="color: #ff0000"><strong>内核级线程模型 </strong></span></p> <p style="text-align: center"><img title="" alt="" src="/uploads/20221230/167239082663aea8aac9adf.jpg"></p> <p>这种模型直接调用操作系统的内核线程,所有线程的创建、终止、切换、同步等操作,都由内核来完成。一个用户态的线程对应一个系统线程,它可以利用多核机制,但上下文切换需要消耗额外的资源。C++就是这种。</p> <p style="text-align: left"><span style="color: #ff0000"><strong>两级线程模型</strong></span></p> <p style="text-align: center"><img title="" alt="" src="/uploads/20221230/167239082763aea8ab20911.jpg"></p> <p>这种模型是介于用户级线程模型和内核级线程模型之间的一种线程模型。这种模型的实现非常复杂,和内核级线程模型类似,一个进程中可以对应多个内核级线程,但是进程中的线程不和内核线程一一对应;这种线程模型会先创建多个内核级线程,然后用自身的用户级线程去对应创建的多个内核级线程,自身的用户级线程需要本身程序去调度,内核级的线程交给操作系统内核去调度。</p> <p>M个用户线程对应N个系统线程,缺点增加了调度器的实现难度。</p> <p>Go语言的线程模型就是一种特殊的两级线程模型(GPM调度模型)。</p> <p><span style="color: #ff0000"><strong>Go线程实现模型MPG </strong></span></p> <p><code>M</code>指的是<code>Machine</code>,一个<code>M</code>直接关联了一个内核线程。由操作系统管理。<br><code>P</code>指的是”processor”,代表了<code>M</code>所需的上下文环境,也是处理用户级代码逻辑的处理器。它负责衔接M和G的调度上下文,将等待执行的G与M对接。<br><code>G</code>指的是<code>Goroutine</code>,其实本质上也是一种轻量级的线程。包括了调用栈,重要的调度信息,例如channel等。</p> <p>P的数量由环境变量中的<code>GOMAXPROCS</code>决定,通常来说它是和核心数对应,例如在4Core的服务器上回启动4个线程。G会有很多个,每个P会将Goroutine从一个就绪的队列中做Pop操作,为了减小锁的竞争,通常情况下每个P会负责一个队列。</p> <p>三者关系如下图所示:</p> <p style="text-align: center"><img title="" alt="" src="/uploads/20221230/167239082763aea8abb91b0.jpg"></p> <p>以上这个图讲的是两个线程(内核线程)的情况。一个M会对应一个内核线程,一个M也会连接一个上下文P,一个上下文P相当于一个“处理器”,一个上下文连接一个或者多个Goroutine。为了运行goroutine,线程必须保存上下文。</p> <p>上下文P(Processor)的数量在启动时设置为<code>GOMAXPROCS</code>环境变量的值或通过运行时函数<code>GOMAXPROCS()</code>。通常情况下,在程序执行期间不会更改。上下文数量固定意味着只有固定数量的线程在任何时候运行Go代码。我们可以使用它来调整Go进程到个人计算机的调用,例如4核PC在4个线程上运行Go代码。</p> <p>图中P正在执行的<code>Goroutine</code>为蓝色的;处于待执行状态的<code>Goroutine</code>为灰色的,灰色的<code>Goroutine</code>形成了一个队列<code>runqueues</code>。</p> <p>Go语言里,启动一个goroutine很容易:go function 就行,所以每有一个go语句被执行,runqueue队列就在其末尾加入一个goroutine,一旦上下文运行goroutine直到调度点,它会从其runqueue中弹出goroutine,设置堆栈和指令指针并开始运行goroutine。</p> <p style="text-align: center"><img title="" alt="" src="/uploads/20221230/167239082863aea8ac25fe4.jpg"></p> <p style="text-align: left"><span style="color: #ff0000"><strong>抛弃P(Processor) </strong></span></p> <p>你可能会想,为什么一定需要一个上下文,我们能不能直接除去上下文,让<code>Goroutine</code>的<code>runqueues</code>挂到M上呢?答案是不行,需要上下文的目的,是让我们可以直接放开其他线程,当遇到内核线程阻塞的时候。</p> <p>一个很简单的例子就是系统调用<code>sysall</code>,一个线程肯定不能同时执行代码和系统调用被阻塞,这个时候,此线程M需要放弃当前的上下文环境P,以便可以让其他的<code>Goroutine</code>被调度执行。</p> <p style="text-align: center"><img title="" alt="" src="/uploads/20221230/167239082863aea8ac92c0b.jpg"></p> <p>如上图左图所示,M0中的G0执行了syscall,然后就创建了一个M1(也有可能来自线程缓存),(转向右图)然后M0丢弃了P,等待syscall的返回值,M1接受了P,将·继续执行<code>Goroutine</code>队列中的其他<code>Goroutine</code>。</p> <p>当系统调用syscall结束后,M0会“偷”一个上下文,如果不成功,M0就把它的Gouroutine G0放到一个全局的runqueue中,将自己置于线程缓存中并进入休眠状态。全局runqueue是各个P在运行完自己的本地的Goroutine runqueue后用来拉取新goroutine的地方。P也会周期性的检查这个全局runqueue上的goroutine,否则,全局runqueue上的goroutines可能得不到执行而饿死。</p> <p><span style="color: #ff0000"><strong>均衡的分配工作 </strong></span></p> <p>按照以上的说法,上下文P会定期的检查全局的goroutine 队列中的goroutine,以便自己在消费掉自身Goroutine队列的时候有事可做。假如全局goroutine队列中的goroutine也没了呢?就从其他运行的中的P的runqueue里偷。</p> <p>每个P中的<code>Goroutine</code>不同导致他们运行的效率和时间也不同,在一个有很多P和M的环境中,不能让一个P跑完自身的<code>Goroutine</code>就没事可做了,因为或许其他的P有很长的<code>goroutine</code>队列要跑,得需要均衡。<br> 该如何解决呢?</p> <p>Go的做法倒也直接,从其他P中偷一半!</p> <p style="text-align: center"><img title="" alt="" src="/uploads/20221230/167239082963aea8ad27723.jpg"></p> <p style="text-align: left"><span style="color: #ff0000"><strong>Goroutine 小结 </strong></span></p> <p>优点:</p> <p>1、开销小</p> <p>POSIX的thread API虽然能够提供丰富的API,例如配置自己的CPU亲和性,申请资源等等,线程在得到了很多与进程相同的控制权的同时,开销也非常的大,在Goroutine中则不需这些额外的开销,所以一个Golang的程序中可以支持10w级别的Goroutine。</p> <p>每个 goroutine (协程) 默认占用内存远比 Java 、C 的线程少(<em>goroutine:</em>2KB ,线程:8MB)</p> <p>2、调度性能好</p> <p>在Golang的程序中,操作系统级别的线程调度,通常不会做出合适的调度决策。例如在GC时,内存必须要达到一个一致的状态。在Goroutine机制里,Golang可以控制Goroutine的调度,从而在一个合适的时间进行GC。</p> <p>在应用层模拟的线程,它避免了上下文切换的额外耗费,兼顾了多线程的优点。简化了高并发程序的复杂度。</p> <p>缺点:</p> <p>协程调度机制无法实现公平调度。</p> <p>终于介绍完啦!小伙伴们,这篇关于《深入Go goroutine理解》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~golang学习网公众号也会发布Golang相关知识,快来关注吧!</p>

- 上一篇
- 基于go手动写个转发代理服务的代码实现

- 下一篇
- golang bufio包中Write方法的深入讲解
-
- 怕孤单的墨镜
- 这篇文章出现的刚刚好,太详细了,赞 👍👍,码住,关注作者大大了!希望作者大大能多写Golang相关的文章。
- 2023-02-27 18:07:38
-
- 爱笑的店员
- 写的不错,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢师傅分享博文!
- 2023-02-24 18:00:26
-
- 体贴的香菇
- 太细致了,已收藏,感谢博主的这篇技术贴,我会继续支持!
- 2023-02-19 13:27:11
-
- 明理的犀牛
- 这篇文章内容真及时,作者大大加油!
- 2023-01-02 03:08:16
-
- Golang · Go教程 | 17小时前 |
- TigervncDebian多用户共享桌面超简单教程
- 482浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Go语言新手必看!切片vs数组,一次搞定这两个核心知识点
- 472浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Docker在Debian上运行超简单教程(保姆级教学)
- 210浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Debian设置hostname踩坑记录:权限问题大揭秘
- 334浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Debian装SQLServer?这些问题你一定要注意!
- 284浏览 收藏
-
- Golang · Go教程 | 2天前 |
- Debian系统下Jenkins自动化部署脚本教学
- 367浏览 收藏
-
- Golang · Go教程 | 2天前 |
- DebianSwap服务器应用实测,这些场景真的用得上!
- 319浏览 收藏
-
- Golang · Go教程 | 2天前 |
- Debian跑TigerVNC实测!真香警告,快来看看性能咋样~
- 171浏览 收藏
-
- Golang · Go教程 | 2天前 |
- 在Debian上玩转SQLServer备份还原,手把手教你一步步操作
- 498浏览 收藏
-
- Golang · Go教程 | 2天前 |
- DebianOverlay不会玩?手把手教你轻松定制化安装
- 258浏览 收藏
-
- Golang · Go教程 | 2天前 |
- Go语言实战:time.Ticker&time.After用法区别及避坑技巧
- 240浏览 收藏
-
- Golang · Go教程 | 2天前 |
- Debian系统如何快速定位&干掉那些讨厌的僵尸进程
- 317浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 茅茅虫AIGC检测
- 茅茅虫AIGC检测,湖南茅茅虫科技有限公司倾力打造,运用NLP技术精准识别AI生成文本,提供论文、专著等学术文本的AIGC检测服务。支持多种格式,生成可视化报告,保障您的学术诚信和内容质量。
- 18次使用
-
- 赛林匹克平台(Challympics)
- 探索赛林匹克平台Challympics,一个聚焦人工智能、算力算法、量子计算等前沿技术的赛事聚合平台。连接产学研用,助力科技创新与产业升级。
- 50次使用
-
- 笔格AIPPT
- SEO 笔格AIPPT是135编辑器推出的AI智能PPT制作平台,依托DeepSeek大模型,实现智能大纲生成、一键PPT生成、AI文字优化、图像生成等功能。免费试用,提升PPT制作效率,适用于商务演示、教育培训等多种场景。
- 57次使用
-
- 稿定PPT
- 告别PPT制作难题!稿定PPT提供海量模板、AI智能生成、在线协作,助您轻松制作专业演示文稿。职场办公、教育学习、企业服务全覆盖,降本增效,释放创意!
- 53次使用
-
- Suno苏诺中文版
- 探索Suno苏诺中文版,一款颠覆传统音乐创作的AI平台。无需专业技能,轻松创作个性化音乐。智能词曲生成、风格迁移、海量音效,释放您的音乐灵感!
- 57次使用
-
- Go语言使用goroutine及通道实现并发详解
- 2023-01-02 221浏览
-
- 如果用go写一个高性能点的聊天服务器应该怎么写?
- 2023-02-16 433浏览
-
- GoLang使goroutine停止的五种方法实例
- 2022-12-30 106浏览
-
- Go语言CSP并发模型goroutine及channel底层实现原理
- 2022-12-31 451浏览
-
- Go并发的方法之goroutine模型与调度策略
- 2023-01-01 331浏览