这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
语言进阶
1.并发和并行
并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
所以我认为它们最关键的点就是:是否是『同时』
2. 进程、线程、协程
进程可理解为电脑中的正在运行一个应用程序,比如爱奇艺。
一个进程好比是一个程序,它是资源分配的最小单位。同一时刻执行的进程数不会超过CPU核心数。不过如果问单核CPU能否运行多进程?答案又是肯定的。单核CPU也可以运行多进程,只不过不是同时的,而是极快地在进程间来回切换实现的多进程。举个简单的例子,就算是十年前的单核CPU的电脑,也可以聊QQ的同时看视频。
电脑中有许多进程需要处于「同时」开启的状态,而利用CPU在进程间的快速切换,可以实现「同时」运行多个程序。而进程切换则意味着需要保留进程切换前的状态,以备切换回去的时候能够继续接着工作。所以进程拥有自己的地址空间,全局变量,文件描述符,各种硬件等等资源。操作系统通过调度CPU去执行进程的记录、回复、切换等等。
线程可以理解为: 一个程序(比如爱奇艺)内包含了多种任务。打个比方,用播放器看视频的时候,视频输出的画面和声音可以认为是两种任务。当你拖动进度条的时候又触发了另外一种任务。拖动进度条会导致画面和声音都发生变化,如果进程里没有线程的话,那么可能发生的情况就是: 【拖动进度条->画面更新->声音更新】,你会明显感到画面和声音和进度条不同步。
如果说进程和进程之间相当于程序与程序之间的关系,那么线程与线程之间就相当于程序内的任务和任务之间的关系。
协程可以理解为:当要下载一个视频时,需要占用一个线程,这个线程是很耗时的I/O行为,如果系统中的某一些线程需要等这个线程执行完才能执行,就会导致很多线程处于空闲状态,此时这些线程没有利用CPU去做运算。
在传统的J2EE系统中都是基于每个请求占用一个线程去完成完整的业务逻辑(包括事务)。所以系统的吞吐能力取决于每个线程的操作耗时。如果遇到很耗时的I/O行为,则整个系统的吞吐立刻下降,因为这个时候线程一直处于阻塞状态,如果线程很多的时候,会存在很多线程处于空闲状态(等待该线程执行完才能执行),造成了资源应用不彻底。
最常见的例子就是JDBC(它是同步阻塞的),这也是为什么很多人都说数据库是瓶颈的原因。这里的耗时其实是让CPU一直在等待I/O返回,说白了线程根本没有利用CPU去做运算,而是处于空转状态。而另外过多的线程,也会带来更多的ContextSwitch开销。
而协程的目的就是当出现长时间的I/O操作时,让出目前的协程调度,执行下一个任务。
知识要点
- 协程既不是进程也不是线程,协程仅是一个特殊的函数。
- 进程、线程的切换者是操作系统,而协程的切换者是编程者或应用程序。
- 进程是CPU资源分配的基本单位,线程是运行和调度的基本单位(CPU上真正运行的是线程)。
- 进程拥有自己的资源空间,线程与CPU资源分配无关,多个线程共享同一进程内的资源。
- 协程的本质是个单线程,它不能同时将单个 CPU 的多个核用上,协程需要和进程配合才能运行在多 CPU 上。
3. go关键字
go 关键字用来创建 goroutine (协程),是实现并发的关键。
并发可能导致一个问题,由于每个协程没有顺序性,在执行一些顺序任务时会导致运算结果错误,这时候需要借助并发安全锁或者以下下文介绍的知识点。
4. Channel
Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯。Channel的创建如下:
channel := make(chan int)
5. WaitGroup
用于实现协程的同步阻塞。
参考文章: