文章也同时发表于medium(`・ω・´)”
稍微提一下,以下所有图画都是我妹妹帮忙画的,希望有帮助大家~
髒沙发LineBot在开发时曾经碰过一个问题,就是在处理大图的时候,有两个步骤,分别是
图片处理:属于CPU操作图片储存:属于IO操作而在上篇文章有介绍Node.js对于IO操作是不会阻塞的,CPU操作是会阻塞的,这是由于Node.js是single thread,没有multi thread的帮忙来处理CPU-bound。
在Golang方面,由于goroutine拥有操作多个thread的能力,所以可以让每个thread来分工CPU操作。
Golang处理的方法遵照MPG模型来处理这类的行为,但如果直接讨论可人会让人一脸矇逼。我们可以介绍「行程到现在高併发的协程」来慢慢了解「出现什么问题,可用什么方法应对」,这样应该会比较好了解。
行程、线程、协程
我们可先有一个CPU核心运作的概念,以单纯的方式来讨论问题,「单一个CPU核心,多执行绪」的运作方式如下
单CPU核心会切成许多的时间切片(timeslicing),一下做thread A的事情,一下做thread B事情,而当做得非常快,就像用来「一个人执行多件事」,举个最近很好看的音速小子来当例子
单CPU核心就是音速小子,因为做事太快,所以同时做切菜、拖地、与蛋头博士泡茶,在一般人的感知里就像同时做这三件事情
有了这个概念后,我们就可以开始讨论
行程
行程是「资源和独立运行的最小单位」,而在以前,因为还没有「multi thread的概念」,所以行程同时也是「执行的最小单位」,这导致一个问题
在切换thread A或thread B的事情时,开销变得很大
我们称这个切换行为为context switch,当音速小子切菜时身上需要有菜刀、大葱、胡萝波,之后拖地又要準备拖把、水桶,再来要泡茶还要準备茶杯,在这每次的切换工作时也要「切换资源」,导致切换的成本变很大,那这要怎么解决呢?
于是就有了线程的概念。
线程
线程这个概念的出现取代了行程「执行的最小单位」的位置,所以要处理多件事情时,可以不需开多个行程,而是「一个行程多个线程」,即
音速小子把这三件事情所需的各个资源都带在身上,这样切换工作时就不用再拿菜刀换拖把了
协程
一切都很美好,直到「周边IO-瓦斯炉」滚茶让音速小子乾等了许久。
由于时间切片是固定的,每次做事的时间也是固定的,这导致音速小子在切换到泡茶这件事时,一直在等周边的瓦斯炉把茶滚好,所以一直看着茶发呆,这样等于白白浪费了这些时间
是否能在滚茶时,让thread的事情从滚茶换成擦茶桌呢?
可以,这时我们可以在程式码这样写
go 滚茶() //这行不会等他完成即会往下一行跑擦完茶桌后,再等茶滚好()
这样音速小子就可以更充分利用时间。
你可能有注意到,我们在程式码里面做了跟「单一个CPU核心,多执行绪」类似的行为,即是「切换事情」。
值得注意的是,
协程的切换是由程式码完成的,而单一个CPU核心线程的切换是由时间切片固定分配的。
这代表协程更加的轻量,因为比起线程它切换的开销更小了,因为它是在原thread里面做切换,不像多线程还要跨thread来切换。
多核心与单核心的差异
大家会发现,单核心始终是一个人,所以很快速切换处理事情的时间,其实同等于认真处理一件一件事情的时间
甚至在快速切换时,因为多了这个切换的开销,往往会比认真一件一件处理来得更久
所以这时就出现了Parallel(平行)这个概念,即是多核心,大家可以看到由于核心的增加可以让同一时间可以处理更多thread
有了这些概念后,欢迎来到现代世界,我们把多核心纳入讨论
多核心的世界里,需要考虑的事情比单核心来得更多,这些核心要怎么分配thread就变成了一门学问,大家要先有一个观念,就是thread分别有
kernel thread:系统层级的thread,由核心所支持,一般来说一个核心支持一个kernal thread,可操作各种底层API与接受user thread所要求要做的事情user thread:使用者层级的thread,无法直接调用底层API,都要透过kernal thread来进行调度现在有查尔斯与音速小子两个核心,现在他们一样要做切菜、拖地、与蛋头博士泡茶这三件事情,他们一样非常快速的做这些事,但三件事情给两个人做势必会遇到「分配」上的问题,所以有以下方法来分配事情。
讲得有点抽象,以下配合几个角色,我们用图来解释
K1: 查尔斯,是kernel thread 1K2: 音速小子,是kernel thread 2U1: 切菜的事情,是user thread 1U2: 拖地的事情,是user thread 2一对一:大家就好好做自己的事
一对多:把全部事情都塞给一个人做
多对多:等等,全部事情大家分配着做才对吧!
做个统整
大家可以发现,原本的行程为什么后来需要线程、内核线程、用户线程、协程,其实最大的目标不外乎就是
让核心在切换事情的成本下降降低核心等待的情形发生,即善用核心的所有时间所以统整下来,可以将行程、线程、协程的关係画张广义的示意图
简单的来说就是依照不同的情境,以不同的层次来分配他们事情
需换人也需换资源:那就换行程需换人但不需换资源:那就换线程不需换人也不需换资源,但不要乾等,先做其他事:那就用协程稍微介绍完这些概念后,接下来会介绍Golang的MPG模型,谢谢你的阅读ヽ(・ω・ゞ)
如有错误欢迎勘正指教,谢谢你的阅读~