教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 简单理解 Goroutine 是如何工作的

简单理解 Goroutine 是如何工作的

发布时间:2022-03-24   编辑:jiaochengji.com
教程集为您提供简单理解 Goroutine 是如何工作的等资源,欢迎您收藏本站,我们将为您提供最新的简单理解 Goroutine 是如何工作的资源

新公司使用 Golang,Golang 的魔力之一就是可以开启成千上万的 goroutine 来处理并发,于是上网看一些简单的关于 Goroutine 的介绍 https://blog.nindalf.com/post...
后期再深入了解 Go Runtime 是如何管理和调度 goroutine

<h2>Go 语言介绍</h2>

如果你第一次接触 GO 编程,或者你对“并发不是并行这句话”没有任何概念,你可以先去看一下 Rob Pike 的演讲excellent talk on the subject,这30分钟的演讲你值得拥有

当人们听到 concurrency(并发) 这个词往往会联想到 parallelism(并行),他们是有关联但完全不一样的概念。在编程的世界中,concurrency 是独立执行的过程的组合,而 parallelism 则是计算任务的同时执行。concurrency 是在一段时间内处理多个任务,parallelism 是同时做多个任务。
Go 可以让我们去编写并发的程序,Go提供了 goroutine 和 goroutine 之间相互通信的能力。本文会更多地关注 goroutine

<h2>Goroutine 和 OS Thread</h2>

Go 使用 goroutine 处理并发,而 Java 则使用 thread(线程)。为了比较 goroutine 和 thread 的区别,我们关注以下三个方面——内存使用,开启和销毁,切换时间

<h3>内存使用</h3>

创建 goroutine 不需要太多的内存,2KB的栈内存足矣,随后会伴随堆内存的分配和释放而增长。线程的开启则需要1MB内存 (goroutine的500倍),并伴随着一片警戒内存页(guard page)的分配,作为线程栈内存之前的警戒区域。
所以,服务器在接收请求的时候可以为每个请求分配一个 goroutine 来处理,并不会有内存问题。但是一请求一线程 (例如Java bio 编程) 模式很有可能会导致 OutOfMemoryError,这不仅仅出现在 Java (bio) 编程中,那些使用OS线程处理并发的语言大多都需要面临这类问题。

<h3>Goroutine 创建和销毁</h3>

线程的创建和销毁有很大的开销,因为我们必须向 OS 请求线程资源,并且在线程完成后归还,维护线程池是一个很好的应对方法。相反,Go Runtime 花费很小的开销就能创建和销毁 goroutine,Go语言也没有提供对 goroutine 的人工管理接口 (意思就是 Go 已经安排好了,你不用瞎操心去管理 goroutine)

<h3>Goroutine 切换开销</h3>

当线程阻塞时,另一个线程会被调度。线程的调度是抢占式的,在切换过程中,调度器必须保存和恢复所有寄存器状态,包括:16个通用寄存器,程序计数器 PC,栈指针 SP,段寄存器,16个 XMM 寄存器,16个 AVX 寄存器,FP 协处理器状态等等。系统频繁地进行线程切换将会带来巨大的开销。
Goroutine 的调度是协作式的(所以被称为 go 协程?)。在进行 goroutine 切换时,只有3个寄存器需要被存储和恢复,他们是程序计数器 PC,栈指针和通用寄存器 DX,开销很小。
Goroutine 的数量通常是巨大的,但这不会影响 goroutine 的切换时间。调度器只会关注运行状态的 goroutine,而忽略阻塞态的 goroutine。Go 跟现代调度器一样都是 O(1) 单位时间复杂度,意味着增加 goroutine 数量不会增加切换时间

<h2>Goroutine 如何执行</h2>

之前提及过,Go Runtime 管理了 goroutine 的创建,调度和销毁。Go Runtime 事先分配了一些线程,所有的 goroutine 都在这些线程上多路复用。在某个时刻,每个线程只会装载执行一个 goroutine,如果那个 goroutine 被阻塞了,他会被换出,并由另一个 goroutine 获得线程执行
因为 goroutine 的调度是协作式的,一个执行无限循环任务的 goroutine 会“饿死”其他在相同线程上的 goroutine(其他 goroutine 无法抢占获得线程)。这个问题在 Go 1.2 中有所缓解,通过在 goroutine 进入一个方法时偶尔去调用调度器(执行调度),所以一个包含方法执行的无限循环是可被抢占的。

<h2>Goroutine 阻塞</h2>

Goroutine 的阻塞不会导致线程的阻塞。即使成千上万的 goroutine 被创建,即使他们大多数都被阻塞了,但只要 Go Runtime 调度其他可用的 goroutine,就不会造成系统资源浪费
用简单的话说,goroutine 是一种更轻量级的 OS 线程的抽象。Go 开发者不需要管理线程,同样OS也感知不到 goroutine 的存在。在 OS 的视角下,Go 程序的就像一个事件驱动的 C 程序。

<h2>线程和CPU</h2>

虽然你不能直接控制 Go Runtime 创建线程的数量,你仍可控制程序使用的 CPU 核心数,通过<code>runtime.GOMAXPROCS(n)</code>来设置 GOMAXPROCS。增加 CPU 核心数也许并不能显著提高程序的性能,但你可以使用工具来找到程序运行最理想的 CPU 核心数

<h2>总结</h2>

像其他语言一样,你应该尽可能避免让多个 goroutine 同时访问共享资源。goroutine 之间不要使用共享内存进行通信,最好的做法是使用 channel 在他们之间传输数据。
最后我强烈建议你阅读 C.A.R.Hoare 的文章 Communicating Sequential Processes。在文章中,他预言单核心 CPU 会最终到达性能瓶颈,芯片制造者将会堆积核心数量。他所表达的观点对 GO 语言的设计有着深远的影响。

到此这篇关于“简单理解 Goroutine 是如何工作的”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
简单理解 Goroutine 是如何工作的
Goroutine的调度分析(一)
goroutine 调度器
Go:Goroutine 的切换过程实际上涉及了什么
golang 深入浅出之 goroutine 理解
golang goroutine 通知_深入golang之---goroutine并发控制与通信
Goroutine是如何工作的
Golang中Goroutine与线程
golang 切片截取 内存泄露_怎么看待Goroutine 泄露
Goroutine 的同步(第三部分)

[关闭]
~ ~