1. 并发与并行的概念及区别
1.1 并发
1.1.1 早期无并发阶段
在计算机发展初期(约数十年前),硬件资源较为有限,CPU 通常仅具备单核心。此时,系统操作员需将待处理任务编排为顺序队列,依次交由 CPU 执行。
在该模式下,任务执行呈严格的串行化:前一个任务未执行完毕(未释放 CPU),后续任务无法开始执行(无法获取 CPU)。
1.1.2 单核并发机制
假设任务 3 需执行 4 秒,任务 4 需 6 秒,在完全串行执行时,总共需要 10 秒。
若任务 3 执行过程中需等待磁盘读取(耗时 2 秒),此时 CPU 处于空闲状态。引入并发机制后,任务 3 可在等待 I/O 时释放 CPU,任务 4 则在此期间获得 CPU 执行权。待任务 3 的磁盘读取完成,再重新申请 CPU 继续执行。
此时,两任务总执行时间缩短为 2 + 6 = 8 秒。其执行流程示意如下:
text
任务3:执行(2s) → 等待I/O(2s) → 执行(2s) 任务4:等待(2s) → 执行(6s)
在单核场景下,系统可通过任务切换在宏观上实现多任务“同时”执行的效果,尽管任一时刻仅有一个任务实际占用 CPU。这种基于时间片轮转的调度机制,显著提升了 CPU 利用率。
因此,可对并发进行如下归纳:
若系统支持多个任务交替执行、在宏观上同时存在,则称该系统支持并发。在单核 CPU 中,多个任务共享 CPU 时间片,通过快速切换营造“并行”假象。
1.1.3 多核并发扩展
随着硬件发展,多核 CPU 逐渐普及,每个核心可独立处理任务,并发机制依然适用,且能力进一步增强:
text
CPU1:任务1 → 任务3 → 任务5 CPU2:任务2 → 任务4 → 任务6
多核架构为并发执行提供了更充分的硬件支持。
1.2 并行
从多核并发的执行模型中可见,不同 CPU 核心可同时执行各自的任务队列。例如,CPU1 执行任务 3 的同时,CPU2 可执行任务 8。
因此,并行可定义为:
若系统支持在同一时刻有多个任务真正同时执行,则称该系统支持并行。并行可视为物理上的同时执行,如同多人并肩前行。
并发与并行的核心区别:
并发关注多个任务在一段时间内的交替执行与共存;
并行强调多个任务在同一时刻的同时执行。
2. 同步与异步:执行模式对比
2.1 生活化示例:医院量血压
同步场景:
小明到医院量血压,排队等候。每位体检者依次接受测量,前一人完成后,下一人才能开始。该过程严格按照时间顺序执行,称为同步执行。
异步场景:
小明排队时因心跳过快,医生建议其休息后再测。他暂离队伍,待状态恢复后重新排队完成测量。在该过程中,小明并未持续占据队列位置,而是在条件满足后重新加入流程,称为异步执行。
2.2 代码示例说明
同步执行代码(TypeScript):
typescript
function testBPSync(name: string) { console.log(name, '测量血压'); } // 依次调用,顺序输出 testBPSync('体检者1'); testBPSync('体检者2'); testBPSync('小明'); testBPSync('体检者3');异步执行代码(使用setTimeout):
typescript
function testBPAsync(name: string) { setTimeout(() => { console.log(name, '测量血压'); }, 2000); } testBPSync('体检者1'); testBPSync('体检者2'); testBPAsync('小明'); // 延迟执行,不阻塞后续代码 testBPSync('体检者3');尽管小明在代码中位于体检者3之前调用,但其测量过程延迟执行,体现了异步特性。
2.3 异步是否等于多线程?
异步并不意味着一定在另一线程执行。例如setTimeout的回调仍在原线程(如浏览器的主线程)执行。
若要在真正独立的线程中执行,可借助多线程编程模型(以 Kotlin 为例):
kotlin
fun testBPAsync(name: String) { thread { println("$name 测量血压(在子线程执行)") } }总结:
同步:代码在单一线程中按调用顺序依次执行。
异步:代码的执行不必等待前序操作完成,可通过回调、事件等方式在当前或不同线程中延后执行。
3. 单线程与多线程:执行单元剖析
3.1 线程与进程的关系
操作系统以进程为资源分配的基本单位,以线程为 CPU 调度的基本单位。线程隶属于进程,共享进程资源,是任务执行的实际载体。
3.2 多线程的价值与挑战
多线程允许同一进程内的多个任务并发或并行执行,例如一个线程处理网络请求,另一个线程执行文件 I/O,从而提升整体效率。
然而,多线程也引入如下问题:
互斥:多线程访问共享资源时需通过锁机制保证数据一致性。
同步:线程间需通过信号、条件变量等机制协调执行顺序。
因此,线程同步与互斥成为多线程编程的核心议题。
单线程模型则天然避免此类问题,通常结合事件循环机制处理多任务。
4. 主线程与子线程:角色与职责
主线程通常是程序的入口线程,负责执行主函数(如main())。在 GUI 应用程序(如 Android、iOS)中,主线程常被指定为UI 线程,承担界面更新、事件响应等职责。为保证交互流畅,主线程应避免执行耗时操作(如网络请求、复杂计算),这些任务应交由子线程处理。
主线程与子线程在本质上是平等的调度单元,其区别主要源于框架或系统赋予的特定角色与约束。
5. JavaScript 执行引擎的单线程设计
JavaScript 在设计上采用单线程执行模型,主要原因包括:
避免多线程环境中的同步与锁机制带来的复杂性。
简化 UI 更新逻辑,确保 DOM 操作的安全性(仅限主线程更新界面)。
通过事件循环(Event Loop)与异步 I/O 机制,在单线程中实现非阻塞并发处理。
理解上述基础概念,有助于进一步深入学习 Promise、setTimeout 及事件循环等异步编程机制。