JS是门单线程语言

多线程语言

像java、python等 它们都是仅支持同步语言,像读取文件、网络请求这种任务 花费时间很长,它们只能长时间等着。
遇到其他紧急任务,Java 可以再开一个线程去处理。

多线程语言的好处是,在同一时间让 cpu 处理多个事情。
充分的利用cpu多核多线程的资源优势。
程序也会执行的更快!

支持多线程的语言有特别多,比如java、python 等

class RunnableDemo implements Runnable {
    private Thread t;
    private final String threadName;
    RunnableDemo( String name) {
        threadName = name;
        System.out.println("创建 " +  threadName );
    }

    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("运行 " +  threadName );
    }

    public void start () {
        System.out.println("启动 " +  threadName );
        if (t == null) {
            t = new Thread (this, threadName);
            t.start ();
        }
    }
}

public class Main {
    // 默认第1个主线程
    public static void main(String[] args) {
        System.out.println("主线程运行" );
        new RunnableDemo( "线程2").start();  // 开启第2个线程(虽然等待3s,但是下边代码会同事执行 不会阻断)
        new RunnableDemo( "线程3").start(); // 开启第3个线程
    }
}

单线程语言

像类似于 js等语言,由于单线程限制,则不能同时调用cpu的多线程 跑任务,它只能一个时间点只能做一件事情,遇到耗时的任务 这种长时间的空闲等待是不可接受的。
所以js便采取了以下的“异步任务回调通知”模式 来达到类似的效果(关于js异步相关的知识 下边单独讲)

console.log("主程序1结束");
setTimeout(() => {
    console.log("异步程序2");
}, 3000);  // 虽然需要耗时3s 单并不会阻塞后续代码执行(这里类似于开线程的效果)
console.log('主程序1结束');

事件循环

事件循环是JavaScript实现异步的一种方法,也是JavaScript的执行机制
JS 本身不实现事件循环机制,这是由它的宿主实现的,浏览器中的事件循环主要是由浏览器来实现,而在 NodeJS 中也有自己的事件循环实现。

当一段JS代码执行的时候,首先它是从磁盘被加载到内存(划分一片空间上下文,也叫做执行栈),然后cpu 去执行,
1· JS 引擎会从上到下、从左到右的执行完成所有的同步任务,期间碰到异步任务的时候放到任务队列中。
2. 所有的同步任务执行完毕,主线程会去看看任务队列是否有任务,若有任务则将任务推到主线程执行
3. 执行结束,会再次去看任务队列是否有任务 以此类推 循环往复 每一次循环就是一个事件周期或称为一次 tick。

在处理任务队列中的任务的时候 还有一个点

  1. 若有微任务,则优先将本轮所有微任务的执行完成,全部结束后 再执行本轮的宏任务

浏览器端的异步实现(事件循环)

以 Chrome 为例,浏览器不仅有多个线程,还有多个进程,如渲染进程、GPU 进程和插件进程等、甚至每个 tab 标签页都是一个独立的渲染进程(所以一个 tab 异常崩溃后,其他 tab 基本不会被影响)。
作为前端开发者,主要重点关注其渲染进程,渲染进程下包含了 JS 引擎线程、HTTP 请求线程和定时器线程等,这些线程为 JS 在浏览器中完成异步任务提供了基础。

JS 是单线程的,也就是同一个时刻只能做一件事情,那么思考:为什么浏览器可以同时执行异步任务呢?

可千万不要搞混了 浏览器本身是多线程的,单线程的只是你的js代码语言本身,
当 JS 需要执行异步任务时,浏览器会另外启动一个线程去执行该任务。
也就是说,“JS 是单线程的”指的是执行 JS 代码的线程只有一个,是浏览器提供的 JS 引擎线程(主线程)。
浏览器中还有定时器线程和 HTTP 请求线程等,这些线程主要不是来跑 JS 代码的。

比如主线程中需要发一个 AJAX 请求,就把这个任务交给另一个浏览器线程(HTTP 请求线程)去真正发送请求,待请求回来了,再将 callback 里需要执行的 JS 回调交给 JS 引擎线程去执行。
即浏览器才是真正执行发送请求这个任务的角色,而 JS 只是负责执行最后的回调处理。
所以这里的异步不是 JS 自身实现的,其实是浏览器为其提供的能力。

webworker

比如说 下边一段代码 非常卡 因为运算量大,这就导致弹窗半天才出来 要怎么解决

let sum = 0;
for (let i = 0; i < 200000; i++) {
    for (let i = 0; i < 10000; i++) {
        sum += Math.random();
    }
}
alert("哈哈");

有很多人可能会这样想

const getSum = () => {
    return new Promise((reslove) => {
        let sum = 0;
        for (let i = 0; i < 20000; i++) {
          for (let i = 0; i < 10000; i++) {
            sum += Math.random();
          }
        }
       reslove(sum)
   });
};
getSum().then(res=>{
    console.log(res);
})
alert("哈哈");

事实上 这样没用
Promise 是用来管理异步编程的,它本身不是异步的 ,promise是同步代码
走的依然是所有js同步代码都公用的JS 引擎线程
所以就会阻塞下边的js代码。

那就找一个 可以触发浏览器单独线程的代码 使其在另一个线程运行就行了,这样就可以了

setTimeout(() => {
    let sum = 0;
    for (let i = 0; i < 20000; i++) {
        for (let i = 0; i < 10000; i++) {
        sum += Math.random();
        }
    }
});
alert("哈哈");

当然最后 还是搬出我们web worker,他可以不借助任何浏览器提供的线程方案,而是自己单独开一个线程
你每次new Worker都会开启一个新线程

// 主线程 index.js
const worker = new Worker("work.js");
// 线程之间通过postMessage进行通信
worker.postMessage(0); // 向子线程发消息
// 监听线程之间message事件
worker.onmessage = function (e) { // 接受子线程的消息
    // 关闭线程
    worker.terminate();
    // 获取计算结束的时间
    console.log("计算结果:", e.data);
    console.log(`代码执行了 ${e.timeStamp} 毫秒`);
};
alert("哈哈");


// 子线程 work.js
this.addEventListener("message", (e) => { // 接受主线程的消息
  let sum = e;
  for (let i = 0; i < 2000; i++) {
    for (let i = 0; i < 100; i++) {
      sum += Math.random();
    }
  }
  postMessage(sum);  // 向主线程发消息
});

web worker总结

这里是web worker的官方文档

WebWorker是HTML5提供的一种浏览器内置的多线程解决方案,就是为js创造多线程的环境。
允许主线程创建webwork线程,将未处理的一些任务分给后者
运行.在js主线程运行的同时,work线程在后台运行,两者互不打扰
等到webwork线程的任务结束后,把结果返回给主线程

web worker的注意点

(1) 同源限制:分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
(2) DOM限制:Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。但是,Worker 线程可以navigator对象和location对象。
(3) 通信联系:Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成
(4) 脚本限制:Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。
(5) 文件限制: Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

postMessage 传递消息的时候 需要传入的是字符串或可以序列化的对象,否则会出现错误。

小结

虽然js语言本身不能多线程,但是浏览器背后可以做,你的每一个异步,对应就是它在后台帮你多线程做任务。
即便这样 最新的webworker 还是给了你开启线程任务的能力!

posted @ 2023-08-02 11:24  丁少华  阅读(91)  评论(0编辑  收藏  举报