并发
并行与并发
并行:在同一时刻,有多个指令在多个 CPU 上同时执行。借助多核 CPU 实现,是真并行。
并发:在同一时刻,有多个指令在单个 CPU 上交替执行,是假并行。
- 宏观:用户体验上,程序在并行执行
- 微观:多个计划任务顺序执行,飞快的切换,轮换使用 CPU 时间片
进程与线程
进程并发
进程并发出现问题:
- 系统开销大,占用资源多,开启进程数量比较少
- 在 Unix/Linux 系统下,还会产生孤儿进程和僵尸进程
在 Unix/Linux 系统中,第一个进程是 init,正常情况下,子进程是通过父进程 fork 创建的,子进程再创建新的进程。
线程并发
线程:LWP(light weight process)轻量级进程,本质仍是进程(Linux 下)。
线程分为用户线程和内核线程:
- 内核线程由操作系统管理和调度,是内核调度实体。它能够直接操作计算机底层资源,充分利用多核 CPU 并行计算的优势,但是线程切换时需要 CPU 切换到内核态,存在一定开销
- 用户线程由用户空间的代码创建、管理和调度,无法被操作系统感知,切换时无须切换到内核态,切换开销小且高效
在用户态,每个线程的存在形式可认为是一块内存地址。这块内存完全私有,既不能访问其他线程内存,也不能被其他线程访问。
在一个程序进程中,所分配内存除了各个线程的私有内存外,还存在着共享内存,共享内存可以被所有线程访问。如果某块共享内存被多个线程任务同时修改,就会导致共享内存的状态不可控。这就是线程安全问题的由来。
进程与线程区别:
- 进程:独立地址空间,拥有 PCB
- 线程:有独立 PCB,但没有独立地址空间(共享)
- 区别在于是否共享地址空间,进程独居,线程合租
- 在 Windows 系统下,线程是最小的执行单位,是被系统独立调度和分派的基本单位,而进程只是给线程提供执行环境(进程是最小的系统资源分配单位)
协程并发
协程:coroutine,是一种用户线程,属于轻量级线程。一个线程(栈 MB 级别)中可以有任意多个协程,但某一时刻只能有一个协程在运行,多个协程分享该线程分配到的计算机资源。
Go 协程
goroutine 是 Go 中的并发实体,是一种轻量级线程(栈 KB 级别),使用通道发送和接收消息。
一个 Go 主线程上,可以起多个 Go 协程(goroutine),其特点:
- 有独立的栈空间
- 共享程序堆空间
- 调度由用户控制
- 一种轻量级线程
主线程是一个物理线程,直接作用在 CPU 上,是重量级的,非常耗 CPU 资源。而协程从主线程开启的,是轻量级线程,是逻辑态,对资源消耗相对小。
其它编程语言的并发机制一般是基于线程,开启过多线程,资源消耗大。而 Go 可以轻松的开启上万个协程,这就突显了 Go 在并发上的优势。
MPG 模式
Go 协程的调度由 Go 运行时管理,称为 MPG 模式(Minimal Preemptive Goroutine)。
- M:操作系统主线程,是物理线程
- P:协程执行需要的上下文
- G:协程
协程通信
Go 提倡通过通信共享内存,而不是通过共享内存实现通信。
对于多协程/线程应用来说,实现同步的目的是防止对共享内存的同时修改,锁机制可用来实现协程间的同步。
Go 语言提供了信号量(Mutex)来实现互斥,有两种锁:sync.Mutex 和 sync.RWMutex。