Processes, threads and goroutines

Processes进程

起初,计算机在批处理模式下一个一个地执行任务。后来在上世纪60年代开发了多任务分时操作系统,在70年代 被广泛用于网络服务器、ftp、telnet、以及后来地httpd上,httpd使用fork子进程的方式来处理每个网连接。

在分时系统上,系统记录当前执行进程的状态,然后在不同进程之间快速切换以便让CPU去执行每个进程,这样便达到了并发执行的效果。切换的过程称作上下文切换

Context switching上下文切换

上下文切换需要付出3个代价:

  • 内核需要为当前执行的进程保存其所有CPU寄存器的内容,然后加载需要执行进程的所有寄存器值。由于进程的切换可能出现在进程执行过程的任意时刻,所有操作系统必须保存所有寄存器内容。
  • 操作系统内核需要清除CPU的虚拟内存到物理内存的映射(TLB cache)。
  • 操作系统上下文切换开销,以及调度器选择进程占用CPU的开销。
Threads线程

由于进程切换代价太大,由此出现了线程。线程的概念和进程差不多,不同的是线程之间共享内存空间,这样的话,相比之下,线程比进程切换轻量一些。
但是线程同样需要上下文切换,许多状态也需要保存和重新加载。
goroutine在线程的基础上又向前走了一步。

Goroutines

goroutines没有依赖操作系统内核去管理分时调度,而是依赖自身的合作调度(cooperatively scheduled)。Go runtime scheduler会合理地调度goroutines,当goroutine出现一下情况时开始调度:

  • channel的send和receive操作被阻塞。
  • 使用go关键字创建新routine
  • 文件或者网络IO操作
  • 垃圾回收循环

在Go运行时中,多个goroutine复用单个操作系统线程。这样,goroutine的创建和切换就非常轻便。正常情况下,一个进程下,可以有上万个goroutine,甚至上十万级。

从语言角度看,Go调度像是函数调用。编译器知道在用的寄存器并且会自动保存他们。
Go调度器使用一个线程时会保持一个goroutine 栈,但是返回时可能时一个不同的goroutine栈。这就好比在多线程程序里,一个线程可能会在任意时刻被抢占。

Go运行时将运行中的goroutine合理安排给一个空闲的线程,这样一个Go进程里就会有相对较少的线程。

栈管理(Stack management)

上面讲到了goroutine如何减少了管理成千上万线程的开销,下面从栈管理上了解一下goroutine。

进程地址空间

这里写图片描述

上图是一个典型的进程内存布局图。在进程的内存空间中,堆内存在下面,和代码区一样。栈分配在虚拟内存的上方,由上向下分配。
为了避免堆内存和栈“撞车“,操作系统会安排一块“guard page“的区域,这块区域不可访问。
这里写图片描述

线程栈

线程之间公用进程的地址空间,每个线程也有自己的栈和“guard page“。
这里写图片描述
由于每个线程需要的栈大小不可预估,为了防止“撞到“gard page,一般会把每个线程栈分配的很大,这样以来整个进程的可用地址空间就很小了。

Goroutine 栈

每个goroutine开始执行的时候从堆中分配一块小的栈,Go1.5版本分配2k。

goroutine不再使用guard pages,而是在函数执行之前先调用一个检测函数来确定是否有足够栈空间,如果有就正常执行。如果没有,Go运行时从堆中分配一块较大的栈,把当前执行的栈内容拷贝到新分配的栈中,然后重新执行函数。
这样的机制保证了goroutine的起始栈可以相对较小,然后随着需求来增加。所以说,在Go程序员来说goroutine相当轻便。

update 2020.08.26

https://nullprogram.com/blog/2015/05/15/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值