纯前端的进军2--多线程与IO
更新日期:
《纯前端的进军》系列主要作为曾经的纯前端,对后台和底层的一些弥补,涉及进程、网络通信,以及对node.js和相关框架的学习。本节我们学习一下单线程与多线程、阻塞、同步/异步和IO等内容。
围绕线程
多线程
多线程,即便处理器只能运行一个线程,操作系统也可以通过快速的在不同线程之间进行切换,由于时间间隔很小,来给用户造成一种多个线程同时运行的假象。这样的程序运行机制被称为软件多线程。
多线程的假象
大部分操作系统都支持多进程并发运行,现代的操作系统几乎都支持同时运行多个任务对于一个CPU而言,它在某个时间点上只能执行一个程序,也就是说,只能运行一个进程,CPU不断地在这些进程之间轮换执行。
因为CPU的执行速度快,所以CPU在多个进程之间轮换执行,但感觉到(宏观上)好像多个进程在同时执行。当然,如果启动的程序足够多,依然可以感觉到程序的运行速度下降。
现代的操作系统都支持多进程的并发(轮换执行),但在具体的实现细节上可能因为硬件和操作系统的不同而采用不同的策略。目前操作系统大多采用效率更高的抢占式多任务策略。
多线程的优点
- 进程之间不能共享内存,但同一进程中的线程之间共享内存非常容易
- 系统创建进程需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高
- 多线程技术使程序的响应速度更快,因为用户界面可以在进行其它工作的同时一直处于活动状态
多线程的缺点
- 等候使用共享资源时造成程序的运行速度变慢。这些共享资源主要是独占性的资源,如打印机等
- 对线程进行管理要求额外的CPU开销。线程的使用会给系统带来上下文切换的额外负担。当这种负担超过一定程度时,多线程的特点主要表现在其缺点上,比如用独立的线程来更新数组内每个元素
- 线程的死锁。即较长时间的等待或资源竞争以及死锁等多线程症状
多线程 VS 多进程
因为并不是说所有情况下用多线程都是好事,因为多线程的情况下,CPU还要花时间去维护,CPU处理各线程的请求时在线程间的切换也要花时间,所以一般情况下是可以不用多线程的,用了有时反而会得不偿失。大多情况下,要用到多线程的主要是需要处理大量的IO操作时或处理的情况需要花大量的时间等等,比如:读写文件、视频图像的采集、处理、显示、保存等。
现代的体系,一般CPU会有多个核心,而多个核心可以同时运行多个不同的线程或者进程。
当每个CPU核心运行一个进程的时候,由于每个进程的资源都独立,所以CPU核心之间切换的时候无需考虑上下文。当每个CPU核心运行一个线程的时候,由于每个线程需要共享资源,所以这些资源必须从CPU的一个核心被复制到另外一个核心,才能继续运算,这占用了额外的开销。换句话说,在CPU为多核的情况下,多线程在性能上不如多进程。
单线程
说到单线程,身为Javascript出身的你我可能再熟悉不过了。
如果有很多任务需要执行,不外乎三种解决方法。
- 排队。因为一个进程一次只能执行一个任务,只好等前面的任务执行完了,再执行后面的任务。
- 新建进程。为每个任务新建一个进程。
- 新建线程。因为进程太耗费资源,所以如今的程序往往允许一个进程包含多个线程,由线程去完成任务。
以JavaScript语言为例,它是一种单线程语言,所有任务都在一个线程上完成,即采用上面的第一种方法。一旦遇到大量任务或者遇到一个耗时的任务,网页就会出现”假死”,因为JavaScript停不下来,也就无法响应用户的行为。
单线程缺点
如上所说,使用单线程,当执行某个耗时或者不能立即完成的任务时,比如:网络通讯、复杂运动,该线程就会暂时停止对其他任务的响应和处理,造成的视觉效果就是程序的“假死”,也就是应用程序被卡在那里无法继续执行。
阻塞与非阻塞、同步与异步
阻塞与非阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
同步与异步
同步是指代码调用IO操作时,必须等待IO操作完成才返回的调用方式。
异步是指代码调用IO操作时,不必等IO操作完成就返回的调用方式。
同步是最原始的调用方式,异步则需要多线程,多CPU或者非阻塞IO的支持。
区分
- 同步与异步是关于指令执行顺序的,阻塞非阻塞是关于线程与进程的。
- 同步和异步关注的是消息通信机制,阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
骚年也来学学讲故事吧–小白拿外卖的故事:
- 同步阻塞:小白点了外卖,在餐馆等,看到外卖好了拿走(小白干等,主动查看外卖进展)
- 同步非阻塞:小白点了外卖,然后去买饮料、买水果,时不时回来看看,等外卖好了拿走(小白不用等,主动查看外卖进展)
- 异步阻塞:小白点了外卖,在餐馆等,外卖好了服务员通知让他拿走(小白干等,外卖进展自动通知)
- 异步非阻塞:小白点了外卖,然后去买饮料、买水果,外卖好了服务员跑来通知让他拿走(小白不用等,外卖进展自动通知)
IO
IO是输入input输出output的首字母缩写形式,直观意思是计算机输入输出,它描述的是计算机的数据流动的过程,因此IO第一大特征是有数据的流动。
从计算机架构上来讲,任何涉及到计算机核心(CPU和内存)与其他设备间的数据转移的过程就是IO。本体就是计算机核心(CPU和内存)。例如从硬盘上读取数据到内存,是一次输入,将内存中的数据写入到硬盘就产生了输出。在计算机的世界里,这就是IO的本质。
UNIX中有五种IO模型,下面用两个阶段说明:
- 等待数据
- 拷贝数据
阻塞式IO(blocking IO)
默认情况下,Linux下的所有socket都是阻塞的。
在I/O执行的两个阶段都被阻塞了——阻塞等待数据,阻塞拷贝数据。
非阻塞式IO(non-blocking IO)
当对一个非阻塞socket执行读操作时,如果内核中的数据还没有准备好,那么它并不会阻塞用户进程,而是立刻返回一个EWOULDBLOCK错误。
在I/O执行的第一个阶段不会阻塞线程,但在第二阶段会阻塞。
IO复用(IO multiplexing)
也称事件驱动IO(event-driven IO),就是在单个线程里同时监控多个套接字,通过select
或poll
轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
进行了两次系统调用,进程先是阻塞在 select/poll 上,再是阻塞在读操作的第二个阶段上。
信号驱动式IO(signal driven IO)
让内核在描述符就绪时发送SIGIO信号通知用户进程。
在等待数据ready期间进程不被阻塞,当收到信号通知时再阻塞并拷贝数据。
异步IO(asynchronous IO)
用户进程在发起aio_read操作后,该系统调用立即返回。然后内核会自己等待数据ready,并自动将数据拷贝到用户内存。整个过程完成以后,内核会给用户进程发送一个信号,通知IO操作已完成。
异步IO的特点是IO执行的两个阶段都由内核去完成,用户进程无需干预,也不会被阻塞。
五种IO模型的比较:
前4种模型的主要区别在于第一阶段,因为它们的第二阶段是一样的:都是阻塞于recvfrom调用,将数据从内核拷贝到用户进程缓冲区。
参考
- 《单线程和多线程的简单理解》
- 多线程- 维基百科
- 怎样理解阻塞非阻塞与同步异步的区别?
- 《同步,异步,阻塞,非阻塞等关系轻松理解》
- 《程序员应该这样理解IO》
- 《IO模型:同步、异步、阻塞、非阻塞》
- 《单线程和多线程的优缺点》
结束语
这一节主要作为对线程的补充,收集了相关的一些IO等的资料。
身为一个JSer,对多线程、阻塞、同步等的理解都很少,整理笔记的同时也强迫学习了一波。
码生艰难,写文不易,给我家猪囤点猫粮了喵~
查看Github有更多内容噢:https://github.com/godbasin
更欢迎来被删的前端游乐场边撸猫边学前端噢
如果你想要关注日常生活中的我,欢迎关注“牧羊的猪”公众号噢