知识点1
线程和协程核心的区别就是处理 IO 阻塞时的不同。
操作系统实现的线程调度是一种通用算法,因为操作系统是无法知道你的线程内部究竟在做什么,它只能分时复用隔一段时间来调度一下每个线程,即使某些线程还处于 IO 阻塞状态,操作系统也得把这个线程切换到 CPU 上去执行一下子【cpu上下文切换】。
协程是自己调度的,它不是“切换开销小”而是基本没有所谓的切换开销,因为你自己是完全知道代码何时会进入阻塞、何时会恢复的,发生阻塞时可以安心去执行其他协程,根本无需在中途切换回来看 IO 是否已经就绪。因为go协程会在阻塞完成后自动进入队列等待被调度,编译时通过插入代码的方式。
总的来说多线程就是你交给 CPU 很多个各式各样的任务,CPU 为了保证这些任务都能正常执行,不得不给每个任务分出一个时间片间隔轮训执行,保证所有任务都在运行。多协程是你把多个任务的 DAG 图都画好了交给 CPU ,CPU 只需要按照你的图按部就班地线性的执行即可。
知识点2
cooperative multitasking 和 asyncio 和 concurrency 和 parallelism 是不同的事情,楼上不少人都搞混了。
单单回答楼主关于协程比线程轻量在哪里:线程( OS thread )是一种 preemptive multitasking 的实现,线程的切换由外部控制,对代码本身透明,不同线程之间相互争抢,当 cpu 切换时需要保存的状态很多(因为操作系统不知道你具体需要哪些,只能都保存下来)。而协程则是 cooperative 的,即你的代码在不需要时(如等待 IO )主动释放控制权,不同协程之间相互协作,切换时主动指明 resume 时需要的状态( async/await transform 成的 state machine 指的就是这些状态),所以更轻量,相应的代码也需要额外的复杂度,并且需要注意 blocking 问题(没有主动释放控制权)。
宣称 OS thread 开销大是因为内核态用户态切换之类的并不准确,因为操作系统也是可以实现 cooperative multitasking 的,只不过这样的系统上运行的用户程序如果出问题(没有主动释放)会卡住整个系统,所以通用操作系统都是采用的 preemptive multitasking 。
知识点3
协程在最初是轻量级线程,而发展到如今在并发编程中其实是和闭包相对应的概念,只是一个是同步语义一个是异步语义。
有栈协程:轻量级线程,Windows 自带的 Fiber ,POSIX 在没有硬件支持下提供的 Pthread 。
共享栈协程:更轻量的轻量级线程,对栈内存需要小心操作,多用于没有原生支持无栈协程的语言。
无栈协程:异步闭包的语法糖,编译后的底层代码就是异步闭包。
知识点4
并发种类:
- 多线程:太慢
- callback:代表作为 Node.js 、python tornado ,boost asio 。但是会陷入 callback 地狱。
- Promise / Future:java, scala, js, 比 callback 好多了,目前是主流技术之一。缺点是要仔细管理闭包的嵌套。
- event loop:一般 c/c++ libev libuv ,还有 python gevent 。心智负担比上述三种都大,但是可以更精细操作、更高效。底层实现一般为 kqueue 和 linux 上的 epoll ,或者 fallback 到 select 。
大名鼎鼎 nginx 就靠 event loop 暴打同时代。
- 协程。
协程一般用 event loop 实现,这种协程就是对 event loop 的抽象。要理解协程,建议稍微学习一下 event loop 。