先撸清楚:并发/并行、单线程/多线程、同步/异步

系列文章:

  1. 先撸清楚:并发/并行、单线程/多线程、同步/异步
  2. 论Promise在前端江湖的地位及作用
  3. 少年,且听我细说 EventLoop/宏任务/微任务是咋玩的?

前言

在编码的过程中经常会遇到并发/并行、同步/异步、单线程/多线程等术语,在分析JS setTimeout/Promise之前先把这些概念厘清。
通过本篇文章,你将了解:

  1. 并发/并行的概念及区别
  2. 同步/异步的概念及区别
  3. 单线程/多线程的概念及区别
  4. 主线程和子线程的概念及区别
  5. JS执行引擎是单线程吗?

1. 并发/并行的概念及区别

1.1 并发

1.1.1 无并发时代

在远古时代(其实就是几十年前),计算机硬件基础比较落后,刚开始只有一个CPU核心。
计算机操作员需要把待完成的事项编排为一个个的任务,依次交给CPU去执行。
image.png
可以看出,任务是依次执行的,上一个任务未执行完(未释放CPU),下个任务是不能执行(获取CPU)的。

1.1.2 单核并发

假设任务3执行时间需要4s,任务4执行时间需要6s,执行完任务3和任务4总共需要10s的时间。
任务3执行的过程中需要等待读取磁盘(磁盘读取需要2s),此时任务3是不怎么占用CPU的,它完全可以先释放CPU专注等待磁盘读取,而此刻任务4就可以有机会得到CPU的执行,任务3磁盘读取返回后继续申请CPU执行未完成的项。
此时任务3、4执行完成总共需要2+6=8s时间。
任务间的执行流程大致如下:
image.png
在一段时间内,任务3和任务4是共存的(任务3执行的过程中,任务4可以插进来),同样的,任务3、任务4执行的过程中也可以插入其它任务5、6…n,此种设计方式可以在一定程度上提高CPU的利用率。
因此,我们简单归纳一下:

  1. 若一个系统支持同时存在多个任务(任务交替运行),则称该系统支持并发
  2. 在单核心的CPU场景下,同一时间只会有一个任务得到执行,CPU将时间分给多个任务执行,CPU执行任务的速度非常快,给人的感觉是多个任务是同时处理完毕的,实际上是分享了CPU的时间片

1.1.3 多核并发

随着硬件的发展,CPU从单核变成多核,每个核心都能独立处理各自的任务,此种场景下,并发的功能依然存在,如下图:
image.png
很容易理解,单核心能够并发,多核心更不在话下了。

1.2 并行

从多核并发的图里,我们知道CPU1的任务队列和CPU2的任务队列可以同时进行。
image.png
如图,同一时刻,任务3在CPU1里执行,任务8在CPU2里执行,通过增加CPU核心数,处理任务的速度更快了。
因此,我们简单归纳如下:

  1. 若一个系统支持同时运行多个任务,则称该系统支持并行。
  2. 并行从字面上意思就是两人在一起肩并肩并排行走。

2. 同步/异步的概念及区别

2.1 量血压的例子

先看一种场景:
小明去医院体检,他决定量血压,此时量血压的人都排成一队了,他排在了队伍的末尾。
如果把医生当做线程,队伍里的每个人都是一个代码行(方法/函数),每个人都按序等待量血压(代码行等待线程执行),前一个人量好了再到下一个人。
我们称这些代码行是按时间先后顺序同步地执行。
image.png

再看另一种场景:
小明去医院体检,他起得比较晚,急匆匆跑到医院的时候,他决定先量血压。此时量血压的人都排成一队了,他排在了队伍的末尾。
轮到他测量血压的时候,医生发现他心跳比较快,于是建议他先休息冷静一会儿再来测,于是他坐到了一旁冷静平复心情。
当他休息的差不多的时候,他又去排队,最终他量好了血压。

image.png

小明确实是排了队,医生那会儿没给他量血压,告诉他满足条件后再来,当满足条件后再重新排队。
我们称此时小明量血压的过程是异步地执行。

2.2 转为TS代码

同步:

//ts 代码
function testBPSync(name:String) {
    console.log(name, 'test bp')
}

testBPSync('体检者1')
testBPSync('体检者2')
testBPSync('小明')
testBPSync('体检者3')

image.png

可以看到打印是依次执行的。

异步:

//ts 代码
function testBPSync(name:String) {
    console.log(name, 'test bp')
}

function testBPAsync(name:String) {
    setTimeout(()=>{
        console.log(name, 'test bp')
    }, 2000)
}

testBPSync('体检者1')
testBPSync('体检者2')
testBPAsync('小明')
testBPSync('体检者3')

image.png
可以看到,虽然小明排在体检者3之前,但是他并没有在体检者3前被量血压,它是过一会才被量,是异步执行的。

2.3 异步==另一个线程?

小明量血压是异步的,但他最终还是同一个医生给他量的,我们把医生比作执行在CPU上的线程,此时虽然是异步的,但并不等于在另一个线程里执行。
上图使用的setTimeout()最终回调还是在主线程里执行。

小明平复了心情后,另一个体检医生让他过去体检,此时他就不在原来的医生那体检了,此种场景对应在另一个线程里执行:

//Kotlin 代码
fun testBPsync(name:String) {
    println("${name} test bp")
}

fun testBPAsync(name:String) {
    thread {
        //在另一个线程里执行
        println("${name} test bp")
    }
}

fun main() {
    testBPSync('体检者1')
    testBPSync('体检者2')
    testBPAsync('小明')
    testBPSync('体检者3')
}

总结来说:

  1. 同步:表示在一个线程里按照时间的先后顺序执行代码
  2. 异步:同步反义词,异步执行的代码可能在同一个线程执行,也可能在另一个线程执行,取决于具体的实现逻辑,异步通常表现为回调的形式。

3. 单线程/多线程的概念及区别

在最开始我们有提到过任务的并发执行,先执行任务的一部分,再执行其余部分,这种"部分"在CPU上是如何调度的呢?
答案是线程。
先了解一个老生常谈的知识:

进程是资源分配的基本单位,线程是CPU任务调度和执行的基本单位

image.png
CPU上执行的是线程,不同进程(任务)的线程、同一进程里的线程交替在CPU上执行,达到了并发的效果。

多线程能够充分利用CPU的能力,比如某个进程里开了两个线程分别去获取网络数据和进行磁盘I/O读写,这俩线程可以同时执行(并行),也可以并发,提升了效率。

但多线程也带来了挑战:

  1. 线程之间的互斥,多线程修改同一内存变量的一致性,通常加锁解决
  2. 线程之间的同步,某个线程修改同一内存变量后通知其它线程变量已经修改,通常等待-通知机制解决

因此多线程编程永恒的话题:线程间的同步和互斥

单线程永远不会担心同步和互斥,单线程通常伴随着事件循环。

有关线程和锁相关请移步系列文章:线程和锁系列

4. 主线程和子线程的概念及区别

承担着进程入口的线程,通常称为主线程。
一般来说我们编写Hello World会有个Main入口,执行此处代码的就是主线程。

image.png

实际上主线程和子线程本质上是没有区别的,都是通过构造线程对象并执行代码。
在Android、iOS设计里,把一个线程选作主线程,那么就会在更新UI的时候做一些校验,判断是否是主线程(UI线程),如果不是那么不允许操作界面元素。并且主线程主要做一些UI布局计算相关的操作(不是渲染线程),同时响应其它线程的事件,比如触摸事件等,因此它一定要比较流畅,不能被同步阻塞,轻则界面卡顿,重则App Crash,因此阻塞的事情交给其它线程(子线程)去做。

5. JS执行引擎是单线程吗?

答案是的。
至于为什么选择单线程,前面也有提到一些。

  1. 单线程不用考虑多线程里的同步互斥问题,无需上锁,无需来回切换线程,简单纯粹
  2. 主线程通常用来更新UI元素,保证唯一的修改源可以减少很多莫名其妙的问题

了解了以上概念,将助力我们进入下篇的Promise/SetTimeout的世界。

  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 多线程和异步I/O适用于需要处理大量并发请求和I/O操作的场景。例如: 1. Web服务器:Web服务器需要同时处理多个请求,而这些请求通常是I/O密集型的,例如读取文件、查询数据库等,因此采用多线程和异步I/O可以提高服务器的性能和吞吐量。 2. 数据库:数据库需要处理大量的读写请求,而这些请求通常是I/O密集型的,因此采用多线程和异步I/O可以提高数据库的性能和响应速度。 3. 大数据处理:对于大规模的数据处理任务,采用多线程和异步I/O可以提高处理速度和效率。 4. GUI应用程序:图形用户界面(GUI)应用程序需要及时响应用户的操作,而这些操作通常是I/O密集型的,例如读取文件、下载数据等,因此采用多线程和异步I/O可以提高应用程序的响应速度和用户体验。 总之,多线程和异步I/O都是提高系统性能和效率的重要手段,但在使用时需要注意线程安全和并发控制等问题。 ### 回答2: 在某些场景下,采用多线程和异步I/O可以有效提升程序的性能和响应速度,以下是几个常见的场景: 1. Web服务器:当有大量请求同时到达服务器时,可利用多线程处理这些请求,每个线程负责一个请求的处理,提高并发处理能力。同时,采用异步I/O可以在等待文件读写或网络通信的时候,切换到其他任务,充分利用CPU资源,让服务器能同时处理更多的请求。 2. 大数据处理:大数据处理往往需要处理海量的数据,使用多线程可以将任务划分成多个子任务,在多个线程上并行执行,从而提高处理速度。此外,采用异步I/O可以在读取或写入大量数据时,不需要等待单个文件或网络操作完成,而是在这个过程中可并行执行其他计算任务,提高整体处理效率。 3. 游戏开发:游戏往往需要同时处理多个任务,如渲染画面、处理用户输入、AI计算等。通过使用多线程,不同的任务可以并行执行,确保游戏的流畅性和响应速度。异步I/O还可以用于游戏资源的加载和保存,避免阻塞主线程,提高游戏性能。 4. 聊天程序/消息推送:在聊天程序或消息推送中,需要实时地接收和发送消息。使用多线程可以在同时接收和处理多个消息,提高程序的并发性。而异步I/O可以在发送和接收消息时,不需要等待消息的同步处理,而是允许并行处理其他消息,提高实时性。 总之,多线程和异步I/O适用于需要处理大量并发任务、大数据量读写或网络通信的场景,能够提高程序的并发性、效率和响应速度。 ### 回答3: 多线程和异步I/O在以下场景中比较适用: 1. 高并发的网络应用:当有大量的并发连接需处理时,采用多线程和异步I/O能够提高应用程序的处理能力和效率。多线程可以同时处理多个连接,而异步I/O可以在等待数据的同时处理其他任务,提高了系统的并发能力。 2. 高性能的数据库访问:在进行数据库访问时,往往需要等待数据库的响应。采用异步I/O可以在等待数据库响应的同时处理其他任务,避免了线程的阻塞。同时,数据库操作往往需要消耗大量的时间,采用多线程可以并发地执行多个数据库查询,提高了系统的响应速度。 3. 大规模数据处理:在进行大规模的数据处理时,采用多线程和异步I/O可以将数据的处理和存储并行进行,提高了处理速度和效率。多线程可以同时处理多个数据片段,而异步I/O可以在等待数据读取或写入的同时执行其他任务。 4. 响应式用户界面:在用户界面的开发中,往往需要同时处理多个用户的输入和输出。采用多线程和异步I/O可以将用户界面的响应和数据的处理分离开来,提高了用户界面的流畅性和响应速度。多线程可以实现并发处理用户的输入事件,而异步I/O可以在等待数据加载或保存的同时处理其他任务。 综上所述,多线程和异步I/O在高并发的网络应用、高性能的数据库访问、大规模数据处理和响应式用户界面等场景中能够提高系统的并发能力、响应速度和效率。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值