在操作系统中,进程和线程的执行都具有并发性。
并发是指向一段时间内,多个任务可以共享系统资源,同时执行。
并行是指从某个时刻开始,多个任务同时执行。
程序的顺序执行
如果在程序中,语句一条语句一条语句顺序排列的,如果系统中只有一个程序,那么程序执行时也是按照程序语句排列先后次序,一条一条地执行下去。这种方式就像工厂生产流水线加工方式那样,这种程序设计方式就叫做顺序程序设计。
在单任务、单处理机系统环境中,内存中只有一道程序作业在执行,一个程序完成后,下一个程序作业才能进入内存继续执行。
这种顺序执行的程序有三个特点:
- 严格顺序执行。每条程序语句的执行都以前一条语句的结束为前提条件
- 一个程序在计算机中运行时独占全部系统资源。只有程序本身的动作才能改变程序的运行环境。
- 程序的执行结果与程序的运行速度无关。即处理机在执行程序任何两条语句之间的停顿,对程序的运算结果不发生影响。
这三个特点概括起来就是程序的封闭性和可再现性。
封闭性:程序一旦运行起来,其计算结果仅仅取决于程序本身,即运行结果唯一。
可再现性:指同一程序可反复执行,且每次执行结果相同。
程序的并发执行
程序的顺序执行限制系统内存中只有一道程序作业,这显然限制了系统性能的发挥,且资源利用率不高。
现代操作系统都支持程序的并发执行。
所谓并发执行是指在同一时间间隔内,多个程序可以“同时”执行。
在单处理机系统中,进程(或线程)通过时间片或者让出控制权来实现任务切换,以达到“同时”运行多个程序的目的。
这种方式叫做程序并发执行,但实际上任何时刻都只有一个任务被执行,其他任务则通过某个算法来排队准备执行。
即宏观上多个程序任务是“同时”执行,但微观上各任务还是一个一个地顺序执行。
程序的并发执行可以使得多个程序可以共享系统资源,提高系统资源利用率,还可以增加系统吞吐量。
同时,系统的并发执行和资源共享也使得系统环境变得非常复杂,不像顺序执行那么简单。
程序的并发执行基本是由操作系统提供的,Go 在语言层面就支持并发特性。
程序的并行执行
和并发执行不同,程序的并行执行是指同一时刻,多个程序可以同时执行。在多处理机系统中,可以让多个进程,或同一进程内的多个线程做到真正意义上的同时执行,它们之间不需要排队(这是在理想情况下,系统中进程(线程)的数量可能超过处理机的数量,这是依然需要排队)。在这种情况下,多个程序才能达到真正意义上的“同时”执行,即并行执行。
进程的概念
进程是在并发环境下,程序的一次动态执行过程。它由进程控制块(PCB)、程序和数据三部分组成,进程在它的生命周期内可能处于执行、就绪、阻塞三种基本状态。
在多任务操作系统中,多个进程可以并发执行,而且进程是系统资源分配的基本单位。系统中每个进程都有自己的内存映像区,且互不影响,所以管理简单,但缺点是系统开销大。所以,系统能同时创建的进程数量是有限的,不能太多。
线程的概念
由于进程的系统开销大,操作系统的设计者们又提出来更小的独立运行的单位——线程。
通过线程来提高系统内程序并发执行的程度,从而进一步提高系统的吞吐量。
在操作系统中,线程是由进程创建的,所以它继承了进程的部分资源,且具有进程的一些基本特征。所以多个线程之间也可以并发执行,且比进程的系统开销小。
但是,和进程一样,线程依然是由系统内核管理的,所以在高并发情况下,系统能创建的线程数量依然有限,效率也不高。
协程的概念
协程本质上是一种用户态线程,不需要操作系统进行抢占式调度,而且在真正的实现中寄存于线程中。因此,协程系统开销极小,可以有效提高线程任务的并发性,避免高并发模式下线程的缺点。
协程最大优势在于其“轻量级”,可以轻松创建上百万个而不会导致系统资源衰竭,而系统最多能创建的进程、线程的数量却少得多。
使用协程的有点是编程简单,结果清晰。但缺点是需要语言的支持,如果语言不支持,则需要用户在程序中自行实现调度。
目前,原生支持协程的语言还很少。
Goroutine
Go 语言在语言级别支持轻量级线程,叫做 Goroutine,Go 语言标准库提供的所有系统调用操作(包括同步I/O操作),都会让出处理机给其他 Goroutine。这使得轻量级线程的切换管理不依赖于系统的进程和线程,也不依赖于 CPU 的核心数量。