Wanmii's Blog.

[Go]Goroutine到底是什么:从进程到Go协程

2018/01/31 Share

本文以尽量精简的方式介绍了进程、线程、子例程、协程和Go协程的相关概念和关系。

进程(Process)

  • 定义:进程是计算机中正在运行程序的实体(实例):在面向进程设计的系统中,进程是程序的基本执行实体;在面向线程设计的系统中,进程本身不是程序运行的基本单位,更多的是作为线程的容器。
  • 概念:进程是一个实体,拥有独立的地址空间,包括文本区域,数据区域,堆栈等。
  • 程序与进程:程序本身只是指令、数据及其组织形式的描述,进程才是真正运行的示例。一个程序可有若干进程,每个进程都可以以同步或异步的方式独立运行。
  • 进程的并行:多个程序可以进程的形式加载到内存中,并通过时分复用,在同一个处理器上表现出同时运行状态。事实上,每个CPU核心任何时间内仅能运行一项进程。
  • 状态:就绪(Ready)– 运行(Running)– 阻塞(Blocked)
  • 进程切换:简单来说就是 收回当前进程占用的处理器并分配给其他进程的过程。 具体分为四步:1、决定是否可以做上下文切换;2、保存上下文环境;3、利用进程调度,选择待执行的进程;4、恢复/装配待执行进程的上下文。 其中上下文环境切换主要是指:PC寄存器的值到私有堆栈;PSW寄存器的值到私有堆栈;SP寄存器的值到进程控制块;其他寄存器的值到私有堆栈。

线程(Thread)

  • 定义:线程是进程的一个实体,是进程中的实际运作单位,是操作系统能够运算调度的最小单位。
  • 概念:线程是一个进程中的不同执行路径。一个标准线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。
  • 进程与线程:线程没有独立的地址空间只能依托进程而存在。同一进程中的线程共享该进程的全部系统资源,如虚拟地址空间,信号处理等。 不过每个线程都有自己独立的堆栈,寄存器环境和本地存储等。
  • 状态:就绪(Ready)– 运行(Running)– 阻塞(Blocked)
  • 线程分类
    • 内核线程:是一个内核实体,系统调度程序可处理内核线程,系统中其他任何线程也可以引用它。除非编写内核扩展或驱动设备程序,否则程序员无法直接控制这些线程。
    • 用户线程:是供程序员处理程序中的多个控制流而使用的实体。线程库提供了用于处理用户线程的API。用户线程只存在于进程中,每个用户线程并不具有自身的线程上下文,无法进行真正意义上的线程并行。
    • 备注:以上划分标准摘录并适用于IBM专有UNIX操作系统;在一些其他系统中,用户线程就称为线程,而轻量级进程指的是内核线程。
  • 线程切换:同一进程中的线程之间是并发运行的;一个线程可以创建和撤销另一个线程;同一进程中的线程进行切换时,只需重新装载待切换线程的私有上下文信息即可。

子例程(Subroutine)

  • 定义:指在计算机程序中,大型程序的一部分代码,由一个或多个语句块组成。
  • 特点:负责完成某项特定的任务,相对其他代码具有一定的独立性。
  • 分类:包括函数(function)、方法(method)等。

协程(Coroutine)

  • 定义:与子例程类似,同为程序组件,亦称为用户级轻量级线程。(未找到明确定义)
  • 子例程与协程:Knuth给出的说法是:“子例程是协程的特例。” 相对于子例程,协程可以有多个出口和入口;
  • 协程关系:程序中可同时建立多个协程并发执行,协程间也可以通过调用相互切换。(协程间的调用是平等关系,而非调用子例程那样的上下级关系。)
  • 支持语言:协程源自Simula和Modula-2语言,目前Lua、Python、Erlang、Scala等均有支持。
  • Lua中的Coroutine方法
    • coroutine.create():创建coroutine,返回coroutine,参数为运行函数;
    • coroutine.resume():开始/重启coroutine,和create配合使用;
    • coroutine.yield():挂起coroutine;
    • coroutine.status():返回当前协程状态;
    • coroutine.running():放回当前协程;

Go协程(Goroutine)

  • 定义:直接引用Go作者之一Rob Pike的说法:“一个Goroutine是一个与其他Goroutines并发运行在同一地址空间的Go函数或方法。一个运行的程序由一个或更多个Goroutine组成。它与线程、协程、进程等不同。它是一个Goroutine。
  • Go协程与协程
    • 执行方式:Go协程往往意味着并发;协程更多是逻辑上的并行。
    • 通信方式:Go协程通过通道(channel)来通信;协程通过让出(yield)和恢复(resume)操作来通信。
  • Go协程与线程
    • 内存分配:每一个线程都有一个固定大小(一般是2M)的内存块做栈;一个Go协程一般只要2K大小的栈开始生命周期,会根据需要动态伸缩。
    • 执行调度:内核线程会被操作系统内核调度,用户线程会被应用进程通过线程库的调度函数调度;Go的运行时则包含其自己的调度器,在用户态进行调度。
    • 身份信息:大多数支持多线程的操作系统中,线程都会被分配一个身份ID便于识别和调度;Go协程并没有身份ID的概念。
  • GOMAXPROCS:Go的调度器使用GOMAXPROCS变量来设置Go协程可分配的操作系统线程数,其默认值是CPU的核心数runtime.GOMAXPROCS(runtime.NumCPU()),程序员可根据自身所需进行设置(绝大多数情况下默认设置性能最优)。

Reference:

CATALOG
  1. 1. 进程(Process)
  2. 2. 线程(Thread)
  3. 3. 子例程(Subroutine)
  4. 4. 协程(Coroutine)
  5. 5. Go协程(Goroutine)