《Go 语言程序设计》读书笔记 (七) Goroutine 与系统线程的区别
goroutine和线程的区别
动态栈
每一个OS线程都有一个固定大小的内存块(一般会是2MB)来做栈,这个栈会用来存储当前正在被调用或挂起(指在调用其它函数时)的函数的内部变量。这个固定大小的栈同时很大又很小。因为2MB的栈对于一个小小的goroutine来说是很大的内存浪费,比如对于我们用到的,一个只是用来WaitGroup之后关闭channel的goroutine来说。而对于go程序来说,同时创建成百上千个gorutine是非常普遍的,如果每一个goroutine都需要这么大的栈的话,那这么多的goroutine就不太可能了。除去大小的问题之外,固定大小的栈对于更复杂或者更深层次的递归函数调用来说显然是不够的。修改固定的大小可以提升空间的利用率允许创建更多的线程,并且可以允许更深的递归调用,不过这两者是没法同时兼备的。
相反,一个goroutine会以一个很小的栈开始其生命周期,一般只需要2KB。一个goroutine的栈,和操作系统线程一样,会保存其活跃或挂起的函数调用的本地变量,但是和OS线程不太一样的是一个goroutine的栈大小并不是固定的;栈的大小会根据需要动态地伸缩。而goroutine的栈的最大值有1GB,比传统的固定大小的线程栈要大得多,尽管一般情况下,大多goroutine都不需要这么大的栈。
goroutine 调度
OS线程会被操作系统内核调度。每几毫秒,一个硬件计时器会中断处理器,这会调用一个叫做scheduler的内核函数。这个函数会挂起当前执行的线程并保存内存中它的寄存器内容,检查线程列表并决定下一次哪个线程可以被运行,并从内存中恢复该线程的寄存器信息,然后恢复执行该线程的现场并开始执行线程。因为操作系统线程是被内核所调度,所以从一个线程向另一个“移动”需要完整的上下文切换,也就是说,保存一个用户线程的状态到内存,恢复另一个线程的到寄存器,然后更新调度器的数据结构。这几步操作很慢,因为其局部性很差需要几次内存访问,并且会增加运行的cpu周期。
Go的运行时包含了其自己的调度器,这个调度器使用了一些技术手段,比如m:n调度,因为其会在n个操作系统线程上多工(调度)m个goroutine。Go调度器的工作和内核的调度是相似的,但是这个调度器只关注单独的Go程序中的goroutine。
和操作系统的线程调度不同的是,Go调度器并不是用一个硬件定时器而是被Go语言"建筑"本身进行调度的。例如当一个goroutine调用了time.Sleep或者被channel调用或者mutex操作阻塞时,调度器会使其进入休眠并开始执行另一个goroutine直到时机到了再去唤醒第一个goroutine。因为因为这种调度方式不需要进入内核的上下文,所以重新调度一个goroutine比调度一个线程代价要低得多。
GOMAXPROCS
Go的调度器使用了一个叫做GOMAXPROCS的变量来决定会有多少个操作系统的线程同时执行Go的代码。其默认的值是运行机器上的CPU的核心数,所以在一个有8个核心的机器上时,调度器一次会在8个OS线程上去调度GO代码。(GOMAXPROCS是前面说的m:n调度中的n)。在休眠中的或者在通信中被阻塞的goroutine是不需要一个对应的线程来做调度的。在I/O中或系统调用中或调用非Go语言函数时,是需要一个对应的操作系统线程的,但是GOMAXPROCS并不需要将这几种情况计数在内。
你可以用GOMAXPROCS的环境变量显式地控制这个参数,或者也可以在运行时用runtime.GOMAXPROCS函数来修改它。我们在下面的小程序中会看到GOMAXPROCS的效果,这个程序会无限打印0和1。
for {go fmt.Print(0)fmt.Print(1)
}$ GOMAXPROCS=1 go run hacker-cliché.go
111111111111111111110000000000000000000011111...$ GOMAXPROCS=2 go run hacker-cliché.go
010101010101010101011001100101011010010100110...
在第一次执行时,最多同时只能有一个goroutine被执行。初始情况下只有main goroutine被执行,所以会打印很多1。过了一段时间后,GO调度器会将其置为休眠,并唤醒另一个goroutine,这时候就开始打印很多0了,在打印的时候,goroutine是被调度到操作系统线程上的。在第二次执行时,我们使用了两个操作系统线程,所以两个goroutine可以一起被执行,以同样的频率交替打印0和1。我们必须强调的是goroutine的调度是受很多因子影响的,而runtime也是在不断地发展演进的,所以这里的你实际得到的结果可能会因为版本的不同而与我们运行的结果有所不同。
Goroutine没有ID号
在大多数支持多线程的操作系统和程序语言中,当前的线程都有一个独特的身份(id),并且这个身份信息可以以一个普通值的形式被被很容易地获取到,典型的可以是一个integer或者指针值。这种情况下我们做一个抽象化的thread-local storage(线程本地存储,多线程编程中不希望其它线程访问的内容)就很容易,只需要以线程的id作为key的一个map就可以解决问题,每一个线程以其id就能从中获取到值,且和其它线程互不冲突。
goroutine没有可以被程序员获取到的身份(id)的概念。这一点是设计上故意而为之,由于thread-local storage总是会被滥用。Go鼓励更为简单的模式,这种模式下参数对函数的影响都是显式的。这样不仅使程序变得更易读,而且会让我们自由地向一些给定的函数分配子任务时不用担心其身份信息影响行为。
《Go 语言程序设计》读书笔记 (七) Goroutine 与系统线程的区别相关推荐
- PHP程序设计读书笔记七
一.比较字符串: 1."=="和"==="的区别: "=="在比较之前会先把非字符串类型的操作数转换成字符串,所以"3" ...
- 《go 语言程序设计》读书笔记(六)Goroutine与系统线程的区别
goroutine和线程的区别 动态栈 每一个OS线程都有一个固定大小的内存块(一般会是2MB)来做栈,这个栈会用来存储当前正在被调用或挂起(指在调用其它函数时)的函数的内部变量.这个固定大小的栈同时 ...
- c语言程序设计读书笔记3000字,C语言程序设计读书笔记题目
读书笔记注意事项: 1. 读书笔记要求至少有六个题目,在一类.二类.三类题目中各选两题,具体题目选择由学生自行选择. 2. 每个题目必须包含所选题目,以及具体题目的程序实现过程,要求每行语句后都需要有 ...
- C语言程序设计---读书笔记汇总(整理中)
目录 一 写在前面 1.1 书籍信息 1.2 简单叙述 二 类型.运算符与表达式 2.1 变量名 2.2 数据类型及长度 2.3 常量 2.4 声明 2.5 算数运算符 2.6 关系运算符与逻辑 ...
- Go语言实战读书笔记
2019独角兽企业重金招聘Python工程师标准>>> Go语言实战读书笔记 第二章 通道(channel).映射(map)和切片(slice)是引用类型.引用类型的对象需要使用ma ...
- Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据
Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据 Oracle PL/SQL 程序设计读书笔记 - 第7章 使用数据 7.1 程序数据的命名 PL/SQL要求在给数据结构命名的时候应 ...
- Oracle PL/SQL 程序设计读书笔记 - 第14章 DML和事务管理
Oracle PL/SQL 程序设计读书笔记 - 第14章 DML和事务管理 Oracle PL/SQL 程序设计读书笔记 - 第14章 DML和事务管理 ACID原则:即一个事务具有原子性.一致性. ...
- c语言用户自己建立数据类型,C语言程序设计学习笔记--用户建立数据类型
C语言程序设计学习笔记--用户建立数据类型. 9.1定义和使用结构体变量 1.定义:C语言允许用户自己建立不同类型数据组合成的组合型数据类型就是结构体 2.形式: struct 结构体名 { 成员表列 ...
- C语言程序设计学习笔记:P1-程序设计与C语言
本系列博客用于记录学习浙江大学翁恺老师的C语言程序设计,系列笔记链接如下: C语言程序设计学习笔记:P1-程序设计与C语言 C语言程序设计学习笔记:P2-计算 C语言程序设计学习笔记:P3-判断 C语 ...
最新文章
- rpm 查看安装包 信息 时间 目录
- 普通页面使用vue.js心得
- BZOJ-3211-花神游历各国(线段树)
- QPixmap: It is not safe to use pixmaps outside the GUI thread原因
- javaTemplates-学习笔记四
- ecs云服务器 系统登陆密码,云服务器ecs系统登录密码
- js面试题:创建一个json对象people,并追加属性:姓名、性别、年龄,追加run方法...
- Clubhouse的不可能三角
- web项目中如何启动爬虫程序?Django+Requests+Ajax制作可视化翻译界面详解
- matlab帧差法图像识别
- mysql中的count函数解释
- 2 VBA链接mysql数据库步骤和代码示例
- Unity实现摄像头录像功能
- linux自动同步onedrive,Linux下同步onedrive
- 基于python的可视化成绩分析
- 检查内存泄露的利器--VLD使用指南
- 乌云飘散后,一群白帽子这样成长
- IMSI、TMSI和P-TMSI
- 导航栏的返回文字修改
- unity地面添加材质球_为Unity3D创建素材(1):图片、着色器、材质球
热门文章
- Scala 中的集合(三):实现一个新的 Collection 类
- 无意间发现我的博客园的年龄有11年了
- Eclipse 中,web项目在Tomcat运行时填写不了Server name
- 【观点】微博的弊端和它的真正意义
- 关于MFC下使用MTL库编译错误的问题
- 将一个包含有2层数据分组的表输出到EXCEL表里,并分组统计
- 构建虚拟工控环境系列 - 罗克韦尔虚拟PLC
- iOS oc 线程 进程,同步异步,并发串行,来捋捋
- Python魔法方法(magic method)细解几个常用魔法方法(下)
- 智能实验室-杀马(Defendio) 4.27.0.951