JS是单线程的,可以执行异步任务实现原理

宏任务有:1)setTimeout, setInterval, setImmediate,2)I/O、3)、UI rendering等

微任务有:1)process.nextTick、2)Promise、3)Object.observe(已废弃)、4)MutationObserver(html5新特性)等

一、单线程

JS是单线程,这句话没错。但更确切的说JS只有一个主线程,但是其实他还有一些其他线程的,当然所有函数任务只可以在主线程执行。

这样命名很不方便?为什么要这样设计?这事有一定原因的。

JS作为浏览器的脚本于是,其最根本的作用就是实现用户与浏览器的交互行为,当一个用户要删除一个A,又同时向A中添加内容的时候,若是多线程,应该怎么样去处理?没有办法处理,若是单线程就很简单了,用户先执行那个操作,我们主线程就进行哪一个,不会出现任何冲突

二、异步

JS明明单线程,但又同时可进行异步操作,这两者不是完全相反的嘛?

没错,JS是单线程,但是JS是在浏览器中运行的脚本语言,它的宿主,浏览器可不是单线程的,这时候,大家又会产生疑问,这两者之间有什么关系呢?下面我们就来讲讲:

1、任务队列

单线程只有前一个任务结束,才能执行下一个任务。如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。

JS语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。

于是呢,任务就被分成两种,一种是同步任务,一种是异步任务

同步任务:只有前一个任务执行完成后,才可执行下一个任务,在主线程中

异步任务:这个队列的所有任务都是不进入主线程执行,而是被浏览提供的线程执行,当执行完毕后就会产生一个回调函数,并且通知主线程,在主线程执行完当前所执行的任务后,就会调取最早通知自己的回调函数,使其进入主线程中执行,比如ajax请求,再主线程中呈现的就是请求结果~

解析:
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件(回调函数callback)。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步。

“任务队列”是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在”任务队列”中添加一个事件,表示相关的异步任务可以进入”执行栈”了。主线程读取”任务队列”,就是读取里面有哪些事件。

“任务队列”中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入”任务队列”,等待主线程读取。

所谓”回调函数”(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。

“任务队列”是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,”任务队列”上第一位的事件就自动进入主线程。但是,由于存在后文提到的”定时器”功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。

这下就很清楚了!原来这就是我们常说的单线程与异步~必须要依赖于浏览器的!



作者:湘兰沅芷
链接:https://www.jianshu.com/p/bb41eeff43f4
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

<think>我们正在讨论单线程环境下的异步操作。根据引用[1][3][4][5],单线程环境(如JavaScript或Flutter)确实可以实现异步操作,而不需要创建新线程。关键在于**事件循环(EventLoop)机制**和**任务队列**。###单线程异步实现原理####1.**事件循环(Event Loop)**-事件循环是单线程异步的核心,它持续检查两个队列:-**宏任务队列(MacrotaskQueue)**:包含整体脚本、`setTimeout`、`setInterval`、I/O操作等。-**任务队列(Microtask Queue)**:包含`Promise.then`、`MutationObserver`等。-执行顺序:1.执行一个宏任务(如初始脚本)。2.执行**所有**任务(直到任务队列清空)。3.渲染UI(如果需要)。4.从宏任务队列取下一个任务执行(循环)。 ####2. **非阻塞I/O与回调**-当遇到异步操作(如网络请求)时:-主线程将操作交给底层系统(浏览器或Node.js的I/O线程池)处理。-**不等待结果**,继续执行后续代码。-操作完成后,回调函数被放入任务队列,等待事件循环调度。 ####3. **示例解析(引用[4][5])**```javascriptconsole.log('script start');//同步任务,直接执行new Promise((resolve)=> {console.log('promise1');//Promise构造函数同步执行resolve();}).then(()=> {console.log('promise2');//then回调是任务});console.log('scriptend');//同步任务,直接执行```**输出顺序**:```script start→ promise1 →script end→ promise2```-执行流程:1.执行同步代码,输出`script start`和`promise1`。2. `then`回调加入任务队列。3.输出`script end`(同步任务结束)。4. **事件循环开始处理任务队列**,输出`promise2`。###为什么单线程实现异步?- **并发假象**:通过将耗时操作委托给外部系统(如浏览器内核的I/O线程),主线程仅负责调度回调,避免阻塞。- **高效调度**:任务优先于宏任务执行,确保高优先级任务(如Promise)及时处理。 ###与传统多线程异步的对比|**特性**|单线程异步(事件循环)|多线程异步| |----------------|------------------------------|--------------------------||线程数量|1个主线程|多个线程| |资源开销|低(无线程切换开销)|高(线程管理、同步成本) ||典型应用|JavaScript、Flutter UI|Java后端服务| |风险|长时间同步任务阻塞整个应用|线程死锁、资源竞争| ###结论单线程环境**完全支持异步操作**,其核心是通过**事件循环+任务队列+非阻塞I/O**实现的。这种模式避免了多线程的复杂性,尤其适合I/O密集型场景(如前端应用)[^1][^3][^4][^5]。--- ###相关问题1. **事件循环中任务和宏任务的区别是什么?**(如执行顺序、常见类型) 2. **如何避免单线程异步中的长时间阻塞?**(如WebWorker的使用场景) 3. **Node.js异步I/O与浏览器有何异同?**(对比Libuv事件循环模型) 4. **Promise和setTimeout的执行优先级如何?**(结合任务/宏任务队列分析)</think>### 单线程环境下如何实现异步操作? 在单线程环境中(如 JavaScript 或 Flutter),异步操作**不需要新建线程**,而是通过以下核心机制实现: --- #### 一、核心机制:事件循环(Event Loop)[^1][^3][^4] 1. **主线程不阻塞** - 遇到异步操作(如网络请求、定时器)时,主线程将其**交给底层系统处理**,继续执行后续代码 - 示例: ```javascript console.log("Start"); // 同步任务 setTimeout(() => { // 异步任务 console.log("Timeout done"); }, 0); console.log("End"); // 同步任务 ``` 输出顺序:`Start → End → Timeout done` 2. **任务队列管理** | 队列类型 | 内容 | 优先级 | |--------------|-------------------------------|--------| | **任务队列** | `Promise.then`、`MutationObserver` | 高 | | **宏任务队列** | `setTimeout`、`setInterval`、I/O回调 | 低 | 3. **执行流程** ```mermaid graph LR A[执行同步任务] --> B{任务队列为空?} B -->|是| C[等待新任务] B -->|否| D[取出任务执行] D --> E{任务队列空?} E -->|否| D E -->|是| F[取出宏任务执行] F --> B ``` --- #### 二、关键实现技术 1. **非阻塞 I/O** [^1][^4] - 当发起网络请求/文件读写时: - 主线程将操作委托给**浏览器内核或操作系统**(实际可能用线程池) - 操作完成后,回调函数被放入任务队列 - 示例(Flutter): ```dart // 主线程继续运行,回调进入队列 http.get('https://api.com/data').then((response) { print(response); // 异步完成时执行 }); ``` 2. **Promise 任务机制** [^4][^5] ```javascript console.log('script start'); // 同步 new Promise((resolve) => { console.log('promise1'); // 同步执行 resolve(); }).then(() => { console.log('promise2'); // 任务异步) }); console.log('script end'); // 同步 ``` **输出顺序**:`script start → promise1 → script end → promise2` 3. **协程(Coroutine)** - 单线程内通过**任务切换**实现"伪并行"(如 Python 的 asyncio): ```python async def fetch_data(): print("Start request") await asyncio.sleep(1) # 异步等待不阻塞线程 print("Data received") ``` --- #### 三、与传统多线程的对比 | **特性** | 单线程异步 | 多线程异步 | |------------------|------------------------|------------------------| | 线程数量 | 1个主线程 | 多个线程 | | 资源开销 | 极低(无线程切换成本) | 高(线程上下文切换) | | 典型应用 | 浏览器/Node.js/Flutter | Java/C# 后端服务 | | 并发风险 | 无死锁/资源竞争 | 需同步机制(如锁) | | 阻塞场景 | 长时间同步计算会卡顿 | 线程可独立阻塞 | > ⚠️ **关键点**:单线程异步的本质是**将阻塞操作转移给系统底层**,通过队列调度回调,而非真正并行[^3][^5]。 --- ### 总结 单线程环境通过: 1. **事件循环**调度任务 2. **任务/宏任务队列**管理回调 3. **非阻塞 I/O 委托**机制 实现高效异步操作,避免了多线程的复杂性,尤其适合 I/O 密集型场景(如 UI 响应、网络服务)[^1][^4]。 --- ### 相关问题 1. **任务和宏任务在事件循环中的执行顺序有何区别?** (如 `Promise` 与 `setTimeout` 的优先级) 2. **单线程异步模型如何处理 CPU 密集型任务?** (讨论 Web Worker 的应用场景) 3. **Node.js异步 I/O 底层是如何实现的?** (Libuv 线程池的作用解析) 4. **在 Flutter 中如何避免异步操作导致的 UI 卡顿?** (Isolate 的使用场景与限制)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值