🥳🥳🥳Worker中还可以创建多个Worker,打开多线程编程的大门

简介: 本篇主要探索 Worker 的更多用法,主要是如何创建多个 Worker ,在 Worker 中如何发送请求,以及如何使用 Worker 进行多线程编程。

上篇讲到了如何使用Worker,介绍了Worker的基本用法,如何创建Worker,如何向Worker发送消息,如何接收Worker的消息,以及如何关闭Worker

本篇探索Worker的更多用法,主要是如何创建多个Worker,和在Worker中如何发送请求,以及如何使用Worker进行多线程编程。

历史回顾:

创建多个 Worker

在上篇文章中,我们已经介绍了如何创建一个Worker,但是在实际开发中,我们可能需要创建多个Worker,这还不简单嘛,多new几个不就好了。

// main.js
const worker1 = new Worker('worker.js')
const worker2 = new Worker('worker.js')
const worker3 = new Worker('worker.js')

worker1.postMessage('worker1')
worker2.postMessage('worker2')
worker3.postMessage('worker3')

// worker.js
self.addEventListener('message', (e) => {
   
   
  console.log(e.data)
})

这里的worker.js是可以复用的,但是数据不会共享,每个Worker都是独立的,我们可以创建多个Worker,但是不要创建太多,因为每个Worker都会占用一定的内存。

image.png

Worker 中创建 Worker

上面说到可以创建多个Worker,但是这里的Worker是指主线程中的Worker,那么我们可以在Worker中创建Worker吗?答案是肯定的。

Worker中,我们可以创建多个Worker,创建方法也很简单,就和我们上面讲到的创建Worker一样,使用new Worker()即可。

这里就将创建的Worker当作主线程来理解就好了,主线程中可以创建WorkerWorker中也可以创建Worker

// worker.js
var worker = new Worker('worker1.js');

worker.postMessage('这里是Worker的主线程发送的消息');

worker.onmessage = (e) => {
   
   
    console.log(e.data);
}

// worker1.js
self.addEventListener('message', (event) => {
   
   
    console.log(event.data);
});

self.postMessage('这里是Worker1的子线程发送的消息');

image.png

到这里我们已经摸到了Worker的奥妙,也打开了前端多线程的编程大门,接下来了就开始进阶吧。

Worker 中引用外部文件

Worker中,我们可以引用外部文件,这样有利于我们将代码进行拆分,方便管理,代码如下:

self.importScripts('worker1.js', 'worker2.js', ...);

这里使用了importScripts方法,它的作用是引入外部文件,可以引入多个文件,文件之间用逗号隔开。

它的实际作用是将外部文件的内容拷贝到Worker中,这样就可以在Worker中使用外部文件中的内容了。

我们先来看一下实际效果:

// worker.js
importScripts('worker1.js', 'worker2.js');
worker1();
worker2();

// worker1.js
var worker1 = () => {
   
   
    console.log('worker1');
}

// worker2.js
var worker2 = () => {
   
   
    console.log('worker2');
}

image.png

可以看到,Worker中引用了外部文件,且可以使用外部文件中的内容。

这样我们的Worker也可以分模块了,不用把所有的代码都写在一个文件中。

importScripts() 方法

importScriptsWorker中的一个全局方法,它的作用是引入外部文件,上面已经体验过了,现在来详解。

importScripts()self.importScripts()是相等的,他们就是一个东西,在Worker的全局作用域中,都是可以省去self的。

除了可以引入外部文件,还可以引入外部的URL,代码如下:

importScripts('https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js');

但是这样会直接报错,因为这个URL不是同源的,所以使用importScripts()引入外部URL时,必须是同源的。

Worker 中使用 XMLHttpRequest

上面我们已经知道了Worker中可以引入外部文件,现在我们还需要使用外部数据,就是请求后台数据,这时候就需要使用ajax了。

但是上面可以看到我们引用Jquery的时候报错了,那么引用axios啥的肯定也是不行的,但是我们可以使用XMLHttpRequest

XMLHttpRequestajax的基础,它是ajax的核心,我们可以使用它来请求后台数据。

// worker.js
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js', true);
xhr.send();
xhr.onreadystatechange = function () {
   
   
    if (xhr.readyState === 4 && xhr.status === 200) {
   
   
        console.log(xhr.responseText);
    }
}

可以看到,我们使用XMLHttpRequest请求了JqueryCDN,并且成功拿到了数据。

Worker 中使用 fetch

上面使用XMLHttpRequest请求数据,这个太底层了,不利于我们的开发,这里庆幸的是,Worker全局中提供了fetch方法,我们可以使用它来请求数据。

// worker.js
fetch('https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js')
    .then(res => res.text())
    .then(data => {
   
   
        console.log(data);
    })

可以看到,我们使用fetch请求了JqueryCDN,并且成功拿到了数据。

fetchajax的新标准,它的优点是:简单、易用、强大,它的缺点是:兼容性不好,不支持IE
抛开IE其实fetch已经被很广泛的使用了,各大浏览器都已经支持了,所以我们可以放心的使用它。
fetchXMLHttpRequest都是用于发送HTTP请求的,但是fetch的语法更加简洁,而且fetch支持Promise,所以我们可以使用async/await来发送请求。
由于本系列文章是为了学习Web Worker,所以这里就不详细介绍fetchXMLHttpRequest了,有兴趣的可以自行查阅相关资料。

实践

现在我们来实践一下,还是拿上次的10万数据的例子来,不过这次我们弄到100万数据,然后使用Worker来处理。

// main.js
const worker = new Worker('worker.js');

worker.onmessage = (event) => {
   
   
    renderData(event.data);
};

const renderData = (data) => {
   
   
    console.log(data);
    // 渲染数据,这里就是网上说的虚拟滚动的实现
};

// 延时进行查询请求,这是为了防止 Worker 把数据发送过来就操作,导致不生效
setTimeout(() => {
   
   
    const params = {
   
   
        name: '1',
        age: ''
    }
    worker.postMessage({
   
   search: params});
}, 1000);

// worker.js
const data = [];
const loadData = () => {
   
   
    // 加载数据
    // 这里可以使用fetch请求数据,现在这里还是空着,留着下次完成
    for (var i = 0; i < 1000000; i++) {
   
   
        data.push({
   
   
            name: 'name' + i,
            age: i
        });
    }

    self.postMessage(data);
};
loadData();

self.addEventListener('message', (event) => {
   
   
    const {
   
   search} = event.data;
    shardQuery(search).then((data) => {
   
   
        self.postMessage(data);
    });
});

// 这里分片处理数据,交给多个线程处理
const shardQuery = (search) => {
   
   
    return new Promise((resolve, reject) => {
   
   
        // 这里定义一个计数器,确保所有线程都执行完毕
        let counter = 0;
        const shardData = [];
        // 这里每个线程处理10万条数据,最终会有10个线程
        const shardSize = 100000;
        for (let i = 0; i < data.length; i += shardSize) {
   
   
            counter += 1;
            const filterWorker = new Worker('filterData.js');
            filterWorker.postMessage({
   
   data: data.slice(i, i + shardSize), search});
            filterWorker.onmessage = (event) => {
   
   
                shardData.push(...event.data);
                counter -= 1;

                // 所有线程都执行完毕,返回结果
                if (counter === 0) {
   
   
                    resolve(shardData);
                }
            };
        }
    });
};


// filterData.js
const filterData = (data, search) => {
   
   
    if (!data || data.length === 0) {
   
   
        return [];
    }

    const {
   
   name, age} = search;
    const result = [];
    for (var i = 0; i < data.length; i++) {
   
   
        const item = data[i];

        let flag = true;
        if (search.name && item.name.indexOf(name) === -1) {
   
   
            flag = false;
        }

        if (search.age && item.age !== age) {
   
   
            flag = false;
        }

        if (flag) {
   
   
            result.push(item);
        }
    }
    return result;
};

self.addEventListener('message', (event) => {
   
   
    const {
   
   data, search} = event.data;
    const result = filterData(data, search);
    self.postMessage(result);
});

可以看到,我们使用Worker来处理数据,这里我们使用了分片处理数据,交给多个线程处理,这样可以提高处理效率。

注意:Worker的创建是需要消耗资源的,所以不要创建太多的Worker,这样会导致性能下降;
我这里创建了10个Worker,理论上是有点多的;
同时我这里的Worker也没有销毁,这里其实可以使用线程池的思想来处理。

总结与预告

到这里,我们已经学习完了Worker的相关知识;

第一篇我们知道怎么使用Worker,知道主线程和子线程的通讯,知道了子线程和主线程的通讯;

这一篇我们学习了创建多个Worker,知道了Worker中如何引入外部资源,同时也进一步的了解了Worker中的一些限制。

接下来我们会认识新的WorkerSeviceWorker,这个Worker可以让我们在离线的情况下也能访问网站,可以实现缓存,实现消息推送等等,让我们一起来期待吧。

相关实践学习
Serverless极速搭建Hexo博客
本场景介绍如何使用阿里云函数计算服务命令行工具快速搭建一个Hexo博客。
目录
相关文章
|
3月前
|
前端开发 JavaScript Go
React中使用worker线程
本文介绍了在React项目中使用worker线程的方法,包括配置webpack以使用worker-loader,创建worker文件,并在组件中使用worker进行大量计算以避免阻塞主线程。
72 0
React中使用worker线程
|
4月前
|
Web App开发 JavaScript 前端开发
[译] 深入理解 Node.js 中的 Worker 线程
[译] 深入理解 Node.js 中的 Worker 线程
|
4月前
|
Cloud Native Java 调度
项目环境测试问题之线程同步器会造成执行完任务的worker等待的情况如何解决
项目环境测试问题之线程同步器会造成执行完任务的worker等待的情况如何解决
|
存储 缓存 前端开发
Web性能优化之Worker线程(下)
{服务工作线程|Service Worker} 基础概念 ⭐️⭐️⭐️ 线程缓存 ⭐️⭐️⭐️⭐️ 线程客户端 生命周期 ⭐️⭐️⭐️ 控制反转与线程持久化 updateViaCache 管理服务文件缓存 ⭐️⭐️⭐️ 线程消息 ⭐️⭐️⭐️ 拦截 fetch 事件 ⭐️⭐️⭐️⭐️⭐️
231 0
|
Web App开发 消息中间件 JavaScript
Web性能优化之Worker线程(上)
Worker 线程简介 {专用工作线程|Dedicated Worker} 专用工作线程 + Webpack {共享工作线程| Shared Workers }
655 0
|
安全 Java
【高并发】通过源码深度分析线程池中Worker线程的执行流程
在《高并发之——通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程》一文中我们深度分析了线程池执行任务的核心流程,在ThreadPoolExecutor类的addWorker(Runnable, boolean)方法中,使用CAS安全的更新线程的数量之后,接下来就是创建新的Worker线程执行任务,所以,我们先来分析下Worker类的源码。
256 0
【高并发】通过源码深度分析线程池中Worker线程的执行流程
|
Java Android开发
【Android 异步操作】线程池 ( Worker 简介 | 线程池中的工作流程 runWorker | 从线程池任务队列中获取任务 getTask )
【Android 异步操作】线程池 ( Worker 简介 | 线程池中的工作流程 runWorker | 从线程池任务队列中获取任务 getTask )
239 0
|
安全 Java
高并发之——通过源码深度分析线程池中Worker线程的执行流程
在《高并发之——通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程》一文中我们深度分析了线程池执行任务的核心流程,在ThreadPoolExecutor类的addWorker(Runnable, boolean)方法中,使用CAS安全的更新线程的数量之后,接下来就是创建新的Worker线程执行任务,所以,我们先来分析下Worker类的源码。
203 0
|
前端开发 JavaScript
【worker】js中的多线程
因为下个项目中要用到一些倒计时的功能,所以就提前准备了一下,省的到时候出现一下界面不友好和一些其他的事情。正好趁着这个机会也加深一下html5中的多线程worker的用法和理解。 Worker简介     JavaScript 语言采用的是单线程模型,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。
1349 0