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语言的设计影响深远。
参考文献
Goroutine是如何工作的?的更多相关文章
- [Go] golang缓冲通道实现管理一组goroutine工作
通道1.当一个资源需要在goroutine之间共享时,通道在goroutine之间架起了一个管道2.无缓冲通道和有缓冲通道,make的第二个参数就是缓冲区大小3.无缓冲通道需要发送和接收都准备好,否则 ...
- 第三章 Goroutine调度策略(16)
本文是<Go语言调度器源代码情景分析>系列的第16篇,也是第三章<Goroutine调度策略>的第1小节. 在调度器概述一节我们提到过,所谓的goroutine调度,是指程序代 ...
- Go part 8 并发编程,goroutine, channel
并发 并发是指的多任务,并发编程含义比较广泛,包含多线程.多进程及分布式程序,这里记录的并发是属于多线程编程 Go 从语言层面上支持了并发的特性,通过 goroutine 来完成,goroutine ...
- goroutine 调度算法
自从开始使用 Go 语言,到现在也有一年多了,虽不算精通,但也算小有理解.在这里简单记录一下我的心得(其实是学习别人的心得) goroutine,Go 语言中 cpu 运行的最小单元,与 lua 携程 ...
- goroutine 分析 协程的调度和执行顺序 并发写
package main import ( "fmt" "runtime" "sync" ) const N = 26 func main( ...
- goroutine 分析 协程的调度和执行顺序 并发写 run in the same address space 内存地址 闭包 存在两种并发 确定性 非确定性的 Go 的协程和通道理所当然的支持确定性的并发方式(
package main import ( "fmt" "runtime" "sync" ) const N = 26 func main( ...
- go语言程序设计学习笔记-1
https://www.jb51.net/article/126998.htm go标准库文档https://studygolang.com/pkgdoc 1. 如果想要再本地直接查看go官方文档,可 ...
- golang 面向对象编程
概述 Golang语言的面向对象与c++,py等语言有所不同,是由于Golang不支持继承:与上述支持聚合和继承的面向对象的语言不同,Golang只支持聚合(也叫做组合)和嵌入.聚合和嵌入的区别: t ...
- Golang 并发Groutine实例解读(二)
go提供了sync包和channel机制来解决协程间的同步与通信. 一.sync.WaitGroup sync包中的WaitGroup实现了一个类似任务队列的结构,你可以向队列中加入任务,任务完成后就 ...
随机推荐
- Swift学习笔记 - URL编码encode与解码decode
使用swift有一段时间了,api的变换造成了很多困扰,下面是关于url编码和解码问题的解决方案 在Swift中URL编码 在Swift中URL编码用到的是String的方法 func addingP ...
- BZOJ 2761: [JLOI2011]不重复数字 hash哈希
题目就不贴了 点我看题 题意:这题题意很简明,就是给一个序列,把序列里相同的删掉,然后输出,按原数列顺序. 思路:这题之前QZZ和ZN大神犇叫我去做,辣时还不会hash,就留着了.最近某夏令营学会了h ...
- mp4格式的视频,编码方式mpeg4,转化为h264
知识点:在使用vcastr3.swf播放器播放flv视频,(同时在html5页面,使用<video>标签时),发现某些MP4格式的代码不能播放 原因:vcastr3.swf和video,不 ...
- 4.scala中的类
版权申明:转载请注明出处.文章来源:http://bigdataer.net/?p=269 排版乱?请移步原文获得更好的阅读体验 1.针对不同字段生成的方法 字段生成的方法备注 var/val nam ...
- [译]JavaScript需要类吗?
[译]JavaScript需要类吗? 原文:http://www.nczonline.net/blog/2012/10/16/does-javascript-need-classes/ 译者注:在 ...
- 自学Java测试代码一数据类型、数组使用
2017-08-22 21:23:37. writer:pprp package test; public class helloWorld { int maxn = 123; //常量,需要定义一个 ...
- tcpdump实用笔记
前言:本文是关于tcpdump抓包的文章,是一篇对于本人而言比较实用轻便的文章,如您需要更详细的介绍,以下链接的文章相比最适合您,而且网络知识要非常扎实才能理解透彻: tcpdump详细介绍 简介:用 ...
- thinkphp3.2.3 + nginx 配置二级域名
使用的是阿里云centOS.74 第一步: 配置urlpath server { listen ; server_name www.xxxx.com xxxx.com; root /data/www/ ...
- js实现类名的添加与移除
方法1:使用className属性: 方法2:使用classList API: //用于匹配类名存在与否 function reg(name){ return new RegExp('(^|\\s)' ...
- ItemsControl的ItemContainerStyle属性
ItemsControl:ListBox,ComboBox,TreeView ItemContainerStyle是用来设置每一个集合控件的Item的样式的属性(即设置每一个项的样式). 使用It ...