Goroutine是如何工作的?

翻译原文链接 转帖/转载请注明出处
英文原文链接 发表于2014/02/24

Go语言

如果你刚刚接触Go语言,或者说你并不理解“并发不等于并行”这句话的含义,那么Rob Pike的讲座值得一看(在youtube上)。这个视频有30分钟长,我保证花30分钟看这段视频是非常值得的。

这里摘录一段他提到的并发和并行之间的区别:“当大家听到并发这个词的时候,他们往往想到的是并行。并行是一个相关,但却完全不同的概念。当我们编程的时候,并发指的是多个独立运行的进程,而并行是指同时运行的多个计算。并发是为了一下子处理很多东西。并行是为了同时做很多事情。” [1] (注:这里的概念有点绕。其实本质的区别在“同时”这个词上。并行强调的时候几个进程同时进行。而并发指的是运行多个进程,但这些进程并不需要同时被执行。它们可以是被调度在同一个CPU分时运行的。)

Go为我们写并发程序提供了便利。它提供了goroutine以及它们之间通信的功能。在这里我们主要讨论goroutine。

Goroutine和线程的区别

Go语言使用的是goroutine,而像Java这样的语言大多使用线程。它们之间的区别是什么呢?让我们从三个方面来看看它们的区别:内存占用,创建和销毁,以及切换开销。

内存占用

创建一个goroutine不需要太多的内存 - 大概2KB左右的栈空间。如果需要更多的栈空间,就从堆里分配额外的空间来使用。2 新创建的线程会占用1MB的内存空间(这大约是goroutine的500倍)。这还不包括守护页(guard page)的空间。守护页是用来保护线程之间的内存空间不会被相互窜改。[7]

因此一个处理很多请求的服务可以为每个请求创建一个goroutine。但是如果为每个请求去创建一个线程,那么它很快就会碰到OutOfMemoryError。这不是Java独有的问题,任何使用操作系统线程作为主要并发手段的编程语言都会碰到这个问题。

创建和销毁的开销

线程需要从操作系统里请求资源并在用完之后释放回去,因此创建和销毁线程的开销非常大。为了避免这些开销,我们通常的做法是维护一个线程池。Goroutine的创建和销毁是由运行环境(runtime)完成的。这些操作的开销就比较小。Go语言不支持手工管理goroutine。

切换开销

当一个线程阻塞的时候,另外一个线程需要被调度到当前处理器上运行。线程的调度是抢占式的(preemptively)。当切换一个线程的时候,调度器需要保存/恢复所有的寄存器。这包括16个通用寄存器,程序指针(program counter),栈指针(stack pointer),段寄存器(segment registers)和16个XMM寄存器,浮点协处理器状态,16个AVX寄存器,所有的特殊模块寄存器(MSR)等。当在线程间快速切换的时候这些开销就变得非常大了。

Goroutine的调度是协同合作式的(cooperatively)。当切换goroutine的时候,调度器只需要保存和恢复三个寄存器 - 程序指针,栈指针和DX。切换的开销就小多了。

前面已经谈到了,goroutine的数目会比线程多很多,但这并不影响切换的时间。有两个原因:第一,只有可以运行的goroutine才会被考虑,正在阻塞的goroutine会被忽略。第二,现代的调度器的复杂度都是O(1)的。这意味着选择的数目(线程或者是goroutine)不会影响切换的时间。[5]

Goroutine的运行

前面谈到,运行环境负责goroutine的创建,调度和销毁。运行环境被会分配一些线程,用来运行所有的goroutine。在任何一个时间点,每个线程只会运行一个goroutine。如果一个goroutine被阻塞,另外一个goroutine会来替换它在对应的线程上运行。[6]

因为goroutine的调度是协同合作式的,如果一个goroutine不停的循环,其它的goroutine就没有机会被调度运行了。在Go 1.2里,这个问题的解决办法是在调用一个函数的时候去偶尔触发Go的调度器。这样一个循环里如果调用了没有被内联的函数,它就可以被抢占了。

Goroutine的阻塞

Goroutine是廉价的,在下面这些阻塞情况下它们也不会造成运行的线程被阻塞:

  • 网络收发

  • 睡眠

  • channel操作

  • sync包里的一些会阻塞的基本操作

即使创建了成千上万的goroutine并且大多数被阻塞了,也不会造成太多的系统资源浪费。因为运行环境会调度另外的goroutine来运行。

简而言之,goroutine是对线程的轻量化抽象。Go语言的程序员不需要直接操作线程。与此同时操作系统也不知道goroutine的存在。从操作系统的角度来看,一个Go程序有点像一个事件驱动的C程序。[5]

线程和处理器

虽然我们不能直接控制运行环境创建多少线程,我们可以设置程序使用的处理器核数。这是通过调用runtime.GOMAXPROCS(n)函数设置GOMAXPROCS变量来实现的。(注:也可以通过直接设置环境变量来控制)。增加处理器核数并不意味着程序性能的提高。这取决于程序本身的设计。你的程序需要用到多少个内核数可以用剖析(profiling)工具来找到答案。

结束语

和其它语言类似,避免多个goroutine同时访问一个共享资源是非常重要的。goroutine之间,最好是用channel来传输数据。有兴趣的可以读一读“do not communicate by sharing memory; instead,share memory by communicating”。

最后,我强烈推荐读一下C. A. R. Hoare写的“Communicating Sequential Processes”。他是个天才。在这篇论文(1978年发表的)里,他预测了单核处理器性能最终会遇到瓶颈,然后芯片制造商们会增加处理器的内核数。他的思想对Go语言的设计影响深远。

参考文献

  1. Concurrency is not parallelism by Rob Pike

  2. Effective Go: Goroutines

  3. Goroutine stack size was decreased from 8kB to 2kB in Go 1.4

  4. Goroutine stacks became contiguous in Go 1.3

  5. Scheduling of goroutines by Dmitry Vyukov

  6. Analysis of the Go runtime scheduler

  7. 5 things that make Go fast by Dave Cheney

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


类型转换 1、int转string 2、string转int 3、string转float 4、用户结构类型转换
package main import s "strings" import "fmt" var p = fmt.Println func main() { p("Contains: ", s.Contains("test&quo
类使用:实现一个people中有一个sayhi的方法调用功能,代码如下: 接口使用:实现上面功能,代码如下:
html代码: beego代码:
1、读取文件信息: 2、读取文件夹下的所有文件: 3、写入文件信息 4、删除文件,成功返回true,失败返回false
配置环境:Windows7+推荐IDE:LiteIDEGO下载地址:http://www.golangtc.com/downloadBeego开发文档地址:http://beego.me/docs/intro/ 安装步骤: 一、GO环境安装 二、配置系统变量 三、Beego安装 一、GO环境安装 根
golang获取程序运行路径:
Golang的文档和社区资源:为什么它可以帮助开发人员快速上手?
Golang:AI 开发者的实用工具
Golang的标准库:为什么它可以大幅度提高开发效率?
Golang的部署和运维:如何将应用程序部署到生产环境中?
高性能AI开发:Golang的优势所在
本篇文章和大家了解一下go语言开发优雅得关闭协程的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。1.简介本文将介绍首先为什么需要主...
这篇文章主要介绍了Go关闭goroutine协程的方法,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。1.简介本文将介绍首先为什么需要主动关闭gor...
本篇文章和大家了解一下go关闭GracefulShutdown服务的几种方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。目录Shutdown方法Regi...
这篇文章主要介绍了Go语言如何实现LRU算法的核心思想和实现过程,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。GO实现Redis的LRU例子常
今天小编给大家分享的是Go简单实现多租户数据库隔离的方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会...
这篇“Linux系统中怎么安装NSQ的Go语言客户端”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希
本文小编为大家详细介绍“怎么在Go语言中实现锁机制”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么在Go语言中实现锁机制”文章能帮助大家解决疑惑,下面...
今天小编给大家分享一下Go语言中interface类型怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考