⚡ 并发编程 #
并发和并行的区别:
- 并发:逻辑上具备同时处理多个任务的能力
- 并行:物理上同时处理多个并发任务的能力
并发 #
一个 CPU 上能同时执行多项任务,在很短时间内,CPU 来回切换任务执行(在某段很短时间内执行程序 a,然后又迅速得切换到程序 b 去执行), 有时间上的重叠(宏观上是同时的,微观仍是顺序执行),这样看起来多个任务像是同时执行,这就是并发。
并行 #
当系统有多个 CPU 时,每个 CPU 同一时刻都运行任务,互不抢占自己所在的 CPU 资源,同时进行,称为并行。并行是并发设计的理想模式。
进程 #
cpu 在切换程序的时候,如果不保存上一个程序的状态(也就是我们常说的 context
–上下文),直接切换下一个程序,就会丢失上一个程序的一系列状态,于是引入了进程这个概念,用以划分好程序运行时所需要的资源。因此进程就是一个程序运行时候的所需要的基本资源单位(也可以说是程序运行的一个实体)。
线程 #
CPU 切换多个进程的时候,会花费不少的时间,因为切换进程需要切换到内核态,而每次调度需要内核态都需要读取用户态的数据,进程一旦多起来,CPU 调度会消耗一大堆资源,因此引入了线程的概念,线程本身几乎不占有资源,他们共享进程里的资源,内核调度起来不会那么像进程切换那么耗费资源。
协程 #
多线程和多进程是并行的基本条件,但是单线程可以利用协程做到并发。协程拥有自己的寄存器上下文和栈。协程在线程上通过主动切换来实现并发,减少了阻塞时间,还避免了线程切换的开销。但协程运行的并发本质上还是串行的。线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作执行者则是用户自身程序。
Go 并发原语 #
Go 的标准库提供了基本的并发原语:Mutex
、RWMutex
、WaitGroup
、Cond
、Context
等。
在并发编程中,如果程序中的一部分会被并发访问或修改,那么,为了避免并发访问导致的意想不到的结果,这部分程序需要被保护起来,这部分被保护起来的程序,就叫做临界区。
临界区就是一个被共享的资源,或者说是一个整体的一组共享资源,比如对数据库的访问、对某一个共享数据结构的操作、对一个 I/O 设备的使用、对一个连接池中的连接的调用,等等。
避免数据竞争的三种方式:
- 不去写变量。读取不可能出现数据竞争。
- 避免从多个 goroutine 访问变量,尽量把变量限定在了一个单独的 goroutine 中。(使用 channel 来共享数据)
- 互斥锁
同步原语的适用场景:
- 共享资源。并发地读写共享资源,会出现数据竞争(data race)的问题,所以需要
Mutex
、RWMutex
这样的并发原语来保护。 - 任务编排。需要 goroutine 按照一定的规律执行,而 goroutine 之间有相互等待或者依赖的顺序关系,常常使用
WaitGroup
或者 channel 来实现。 - 消息传递。信息交流以及不同的 goroutine 之间的线程安全的数据交流,常常使用 channel 来实现。
标准库
sync
提供的同步原语都是不能复制的。