参考网址
juejin.cn/post/723954…
概念
1/进程process
每个进程都有自己的独立内存空间,拥有自己独立的地址空间、独立的堆和栈,既不共享堆,亦不共享栈。
一个程序至少有一个进程,一个进程至少有一个线程。
进程切换只发生在内核态。
2/线程thread
线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,是由操作系统调度,是操作系统调度(`CPU调度`)执行的最小单位。
对于进程和线程,都是有内核进行调度,有 CPU 时间片的概念, 进行抢占式调度。
内核由系统内核进行调度, 系统为了实现并发, 会不断地切换线程执行, 由此会带来线程的上下文切换。
3/协程
协程和线程一样共享堆,不共享栈,协程是由程序员在协程的代码中显示调度。
协程(用户态线程)是对内核透明的, 也就是系统完全不知道有协程的存在, 完全由用户自己的程序进行调度。
在栈大小分配方便,且每个协程占用的默认占用内存很小,只有 `2kb` ,而线程需要 `8mb`,相较于线程,因为协程是对内核透明的,所以栈空间大小可以按需增大减小。
4/并发
多线程程序在单核上运行
5/并行
多线程程序在多核上运行
协程与线程主要区别是它将不再被内核调度,而是交给了程序自己而线程是将自己交给内核调度,所以golang中就会有调度器的存在。
1/进程
在计算机中,单个 `CPU` 架构下,每个 `CPU` 同时只能运行一个任务,也就是同时只能执行一个计算。
如果一个进程在跑着,就把唯一一个 CPU 给完全占住,显然是不合理的。
而且很大概率上,`CPU` 被阻塞了,不是因为计算量大,而是因为网络阻塞。
如果此时 `CPU` 一直被阻塞着,其他进程无法使用,那么计算机资源就是浪费了。
这就出现了多进程调用了。多进程就是指计算机系统可以同时执行多个进程,从一个进程到另外一个进程的转换是由操作系统内核管理的,一般是同时运行多个软件。
比如我们在一个单核的电脑上同时听歌和写word文档,这是2个软件,其实电脑同时只能跑一个软件,但是操作系统会在极短的时间内来回的切换,这样就好像是同时在执行2个任务,其实就是一个。但是我们用户完全没有感知。
2/线程
有了多进程,为什么还要线程?原因如下:
- 进程间的信息难以共享数据,父子进程并未共享内存,需要通过进程间通信(IPC),在进程间进行信息交换,性能开销较大。
- 创建进程(一般是调用 fork 方法)的性能开销较大。
在一个进程内,可以设置多个执行单元,这个执行单元都运行在进程的上下文中,共享着同样的代码和全局数据,由于是在全局共享的,就不存在像进程间信息交换的性能损耗,所以性能和效率就更高了。这个运行在进程中的执行单元就是线程。
3/协程
官方给的解释如下
Goroutines
是使并发易于使用的一部分。 这个想法已经存在了一段时间,就是将独立执行的函数(协程)多路复用到一组线程上。 当协程阻塞时,例如通过调用阻塞系统调用,运行时会自动将同一操作系统线程上的其他协程移动到不同的可运行线程,这样它们就不会被阻塞。 程序员看不到这些,这就是重点。 我们称之为 goroutines 的结果可能非常便宜:除了堆栈的内存之外,它们的开销很小,只有几千字节。
为了使堆栈变小,
Go
的运行时使用可调整大小的有界堆栈。 一个新创建的goroutine
被赋予几千字节,这几乎总是足够的。 如果不是,运行时会自动增加(和缩小)用于存储堆栈的内存,从而允许许多goroutine
存在于适度的内存中。 每个函数调用的CPU
开销平均约为三个廉价指令。 在同一个地址空间中创建数十万个goroutine
是很实际的。 如果goroutines
只是线程,系统资源会以更少的数量耗尽。
从官方的解释中可以看到,协程是通过多路复用到一组线程上,所以本质上,协程就是轻量级的线程。但是必须要区分的一点是,协程是用用户态的,进程跟线程都是内核态,这点非常重要,这也是协程为什么高效的原因。
协程的优势如下:
- 节省 `CPU`:避免系统内核级的线程频繁切换,造成的 `CPU` 资源浪费。协程是用户态的线程,用户可以自行控制协程的创建于销毁,极大程度避免了系统级线程上下文切换造成的资源浪费。
- 节约内存:在 `64` 位的 `Linux` 中,一个线程需要分配 `8MB` 栈内存和 `64MB` 堆内存,系统内存的制约导致我们无法开启更多线程实现高并发。而在协程编程模式下,只需要几千字节(`执行Go协程只需要极少的栈内存,大概4~5KB,默认情况下,线程栈的大小为1MB`)可以轻松有十几万协程,这是线程无法比拟的。
- 开发效率:使用协程在开发程序之中,可以很方便的将一些耗时的 `IO` 操作异步化,例如写文件、耗时 `IO` 请求等。并且它们并不是被操作系统所调度执行,而是程序员手动可以进行调度的。
- 高效率:协程之间的切换发生在用户态,在用户态没有时钟中断,系统调用等机制,因此效率高。
4/gmp调度器
- `G` 表示:`goroutine`,即 `Go` 协程,每个 `go` 关键字都会创建一个协程。
- `M` 表示:`thread` 内核级线程,所有的 `G` 都要放在 `M` 上才能运行。
- `P` 表示:`processor` 处理器,调度 `G` 到 `M` 上,其维护了一个队列,存储了所有需要它来调度的`G`。
`Goroutine` 调度器 `P` 和 `OS` 调度器是通过 `M` 结合起来的,每个 `M` 都代表了 `1` 个内核线程,`OS` 调度器负责把内核线程分配到 `CPU` 的核上执行,
5/线程thread和协程goroutine的映射关系
在上面的 `Golang` 官方关于协程的解释中提到:
将独立执行的函数(协程)多路复用到一组线程上。
当协程阻塞时,例如通过调用阻塞系统调用,运行时会自动将同一操作系统线程上的其他协程移动到不同的可运行线程,这样它们就不会被阻塞。
也就是说,协程的执行是需要通过线程来先实现的。下图表示的映射关系: