Skip to content

单线程和事件轮询机制

进程和线程

js
进程:车间
    程序的一次执行, 它占有一片独有的内存空间

线程:工人
    CPU的基本调度单位, 是程序执行的一个完整流程


进程和线程:
  * 一个进程中一般至少有一个运行的线程: 主线程。
  * 一个进程中也可以同时运行多个线程, 我们会说程序是多线程运行的。
  * 一个进程内的数据可以供其中的多个线程直接共享。
  * 多个进程之间的数据是不能直接共享的。

进程之间的通信:socket文件

进程是计算机内最小的单位

image-20240318154929283

JS 单线程运行

js
1. 如何证明JavaScript是单线程执行?
   设置了定时器,定时器的回调函数会等到主线程空闲且时间到执行;
   如果主线程没有空闲下来,即使定时器的时间到了,回调函数也不会执行(等到主线程空闲)。

2. 为什么JavaScript选择单线程?
   多线程会有线程调度以及线程开启关闭的开销
   JavaScript主要在浏览器端操作DOM完成特效,如果不是单线程,不好解决页面渲染的同步问题。

同步任务和异步任务

js
同步任务:
按照顺序,一步一步地执行,执行完上一个任务再执行下一个任务

异步任务:
需要满足条件且主线程空闲才可以执行,在等待异步任务满足条件的过程中,同步任务继续执行,异步任务会在同步任务完成后执行
异步任务都是回调函数的形式, 回调函数不一定都是异步任务

JS中的异步任务有哪些:
1. 定时器的回调函数
2. DOM事件的回调函数
3. Ajax的回调函数 前后端交互
4. Promise的回调涵数
....
--------------------------------
// 创建定时器,同步任务
// 定时器的回调函数是异步任务,需要满足条件且主线程空闲才会执行
setTimeout(function() {
    console.log('定时器执行');
});

// 同步任务
console.log(100);
console.log(200);

事件轮询机制

事件轮询(Event Loop)是 JS 实现异步的具体解决方案,同步代码直接执行,异步函数或代码块先放在异步队列中,待同步函数执行完毕,轮询执行异步队列的函数。

js
1、执行栈(调用栈)
   主线程里就是一个执行栈,所有的任务(执行上下文对象)都要放入执行栈执行
    
2、异步任务管理模块
   判断异步任务是否满足了执行条件,分为:
   定时器管理模块
   DOM事件管理模块
   Ajax管理模块
   ...
   如果满足了异步任务管理模块,会将异步任务放入回调队列,等待执行

3. 回调队列
   队列是一种数据存储结构,特点是先进先出,后进后出
   回调队列存放等待执行的异步任务

4. 事件轮询模块
   时刻监听主线程(执行栈)是否空闲,一旦空闲,从回调队列中取出异步任务,放入主线程执行
js
执行栈(调用栈):要执行的代码进入执行栈

回调队列: 计时器过期或者事件触发亦或者接收到ajax响应,现在回调被推送到回调队列。但是回调不会立即执行,这就是事件轮询开始的地方。

管理模块: DOM事件管理、定时器管理、ajax请求管理

事件轮询: 事件轮询的工作是监听执行堆栈,并确定执行堆栈是否为空。如果执行堆栈是空的,它将检查回调队列,看看是否有任何挂起的回调等待执行。

image-20240318155052281

js
JS主线程(执行栈)是车间主任
浏览器管理模块是 负责异步任务的管理,在计时和监听用户触发事件,当触发或计时器到点时,会把回调送到回调队列
事件轮询一直在询问主线程是否空闲,一旦空闲,就把执行队列的任务退给主线程工作。

一道经典题目:

js
//问题描述:请写出最终的输出值,并解释原因
var value1 = 0, value2 = 0, value3 = 0;
for (var i = 1; i <= 3; i++) {
    var i2 = i;
    (function () {
        var i3 = i;
// 每次循环都开启一个定时器 但是定时器的回调函数在循环结束后才会执行!!!!!
        setTimeout(function () {
            value1 += i;
            value2 += i2;
            value3 += i3;
        }, 1);
    })();
}
// for 循环结束之后 全局变量i的值是4, 全局变量i2的值是3
setTimeout(function () {
    console.log(value1);     // 12
    console.log(value2);     // 9  
    console.log(value3);     // 6
}, 100);

// i 是全局变量,循环结束了i的值为4,然后开始执行回调函数创建了3个回调函数,对应4+4+4

//  i2 也是全局变量,但是循环结束时,i2的值为3,执行回调函数,创建了3个回调函数对应3+3+3

// i3 是局部变量,循环结束时,创建了三个回调函数,每个回调函数中的I3,是上层回调函数作用域的值分别是1+2+3

JS 实现多线程(了解)

js
Worker 构造函数
Worker.prototype.postMessage()  向分线程发送数据
Worker.prototype.onmessage      监听分线程的消息

创建子线程
new Worker('[路径]');
利用Worker可以实现多线程运算符
通过实例化一个Worker,创建一个子线程
子线程里面不允许操作DOM,也没有window
worker适合场景:
    把耗时的计算放在分线程,不会影响主线程的其他工作
    如果耗时的计算在主线程,导致页面卡顿(甚至崩溃)

worker的缺点:
    ① 无法操作DOM
    ② 无法跨域
    ③ 兼容性(不是所有的浏览器都可以使用)
Worker 构造函数
Worker.prototype.postMessage()  向分线程发送数据
Worker.prototype.onmessage      监听分线程的消息

image-20240318155129207

js
// 获取相关元素
var input01 = document.querySelector('#input01');
var input02 = document.querySelector('#input02');
var btn = document.querySelector('#btn');
var resBox = document.querySelector('#res');

// 创建子线程  子线程的代码会执行
var worker = new Worker('./child.js');

// 点击计算按钮 向子线程发送数据
btn.onclick = function() {
    // 定义向子线程发送的数据 ‘+’ String 自动转换为 Number
    var nums = [+input01.value, +input02.value] 
    // 向子线发送数据
    worker.postMessage(nums);
};


// 监听子线程发过来数据
worker.onmessage = function(event) {
    resBox.innerHTML = event.data;
}

子线程

js
// 监听主线程发过来的数据
onmessage = function(event) {

    //console.log('接收到主线程数据了:', event);

    // 根据接收到的数据进行计算
    var res = event.data[0] + event.data[1];

    // 将数据发送到主线程
    postMessage(res);
};