javascript如何使用Worker多线程解决大量计算的问题?

时间: 作者:admin 浏览:

还记得之前的一片关于栅格布局寻位的算法文章吗?

vue-grid-layout自动添加新元素在空白处算法

真的在项目中遇到需要大量计算的问题怎么办?
在我看来,至少有两种解法:

一、让后端提供接口,将算法逻辑写在服务端,前端请求返回计算结果即可

这种做法实际是将算法交给后端帮我们执行,因为对比起浏览器,服务器处理大数据的能力强太多了,所以这是一种可行的做法;

二、前端利用web Worker多线程处理

我们知道javascript是单线程的,但浏览器是多线程的,javascript提供的Worker类就可以用来唤起多线程,在特定的场景下,Worker确实可以提升用户体验,就拿开头文章的应用场景吧:

一个页面左侧有一块画布,右侧有一个卡片列表,点击列表某个卡片,弹出这个卡片的属性配置抽屉(drawer),配置完属性后,点击抽屉的确定按钮,将卡片添加到画布上,依次循环操作,形成栅格布局…

如果画布里已有大量的卡片,各卡片位置不确定,产品的需求是:插入卡片时,自动寻位,如果画布前面有足够的空白放下新添加的卡片,则优先添加到前面的空白,否则放置在所有卡片最后一行的行头

因此我们想出了文章开头的两个方法:addNewItem()getPositionForNewItem()

但使用过程中有个问题:当点击抽屉确定按钮后,开始执行寻位方法getPositionForNewItem(),因为其中的大量算法,导致点击确定按钮后页面卡在这里很久都没处理完,需要等待几秒甚至十几秒,体验急剧下降;

于是我们又想了一个解决办法:在用户点击弹出属性抽屉后我们就立马执行寻位算法

因为用户配置新卡片属性肯定需要不少时间,利用这个时机,我们可以抢先计算出卡片将要放置的位置值(x, y),用户配置完卡片属性后,一点击确定按钮,新卡片马上就会添加到画布,根本不需等待;

最后,还有一个问题,假如寻位算法还没跑完,而用户又点击了确定按钮怎么办呢,答案是继续等待算法执行完,因此还要用到事件监听eventbus,解决用户点击时机的问题,下面代码均有涉及;

好,清楚了问题,直接上代码:

export default {
    data(){
        return {
            layoutData: []
            workerFlag: false,
            newPosition: null
        }
    },
    methods:{
        //开头文章的基础上修改
        async addNewItem(item){
            item.i = this.layoutData.length//i的值可以根据自己实际情况设置
            const {x, y} = window.Worker?await this.awaitWorkerCalcPosition() : this.getPositionForNewCard(item)
            item.x = x
            item.y = y
            //插入到画布
            this.layoutData.push( item )
            //到此完结
        },
        awaitWorkerCalcPostion(){
            return new Promise((resolve, reject)=>{
                if(this.newPosition){
                    this.workerFlag = false
                    resolve(this.newPosition)
                }else{
                    this.workerFlag = true
                    this.$bus.$on("worker", pos =>{
                        resolve(pos)
                    })
                }
            })
        },
        getPositionForNewCardByWorker(row){
            if(window.Worker){
                this.workerFlag = false
                this.newPosition = null
                //文件要放在相对生产环境根目录下,vue项目则放在public目录下,这里是public/js/calcNewPositionWorker.js
                const worker = new Worker("js/calcNewPositionWorker.js")
                worker.postMessage({...算法需要的参数})
                worker.onmessage = (e)=>{
                    this.newPosition = e && e.data
                    this.workerFlag && this.$bus.$emit("worker", this.newPosition)
                    worker.terminate()//关闭worker线程
                }
                worker.onerror = (e)=>{
                    worker.terminate()
                }
            }
        },
        getPositionForNewCard(newItem){
            //参考开头文章
            //这里写多一份是为了给没有Worker的浏览器用的
        }
    }
}

而路径“js/calcNewPositionWorker.js”对应的文件内容如下:

//onmessage方法是系统自带的,self.onmessage,self可以省略,(跟window对象一个意思)

onmessage = function(e){
    //用以接收发送过来的算法需要的参数
    const params = e && e.data
    const pos = getPositionForNewCard(params)
    postMessage(pos)
    self.close()
}

function getPositionForNewCard(params){
    //具体参考文章开头文章
}

过程写完了,捋一下这几个方法的执行顺序:

首先,用户一点击右侧卡片列表弹出抽屉时,立马执行getPositionForNewCardByWorker()方法(用户配置属性中…),实例worker的postMessage方法给calcNewPositionWorker.js文件发送了一个信息并监听,文件中的算法执行完后执行postMessage()方法把结果(pos)发回来给worker.onmessage,赋值给this.newPosition

而,用户不知道什么时候配置完属性,当用户点击确定按钮时,执行addNewItem()方法,走有Worker的逻辑awaitWorkerCalcPosition()方法,假如this.newPosition已经有值,说明算法已经执行完并返回了结果,直接resolve()返回;没值,说明算法还没执行完,需要等待”worker”事件有返回再resolve()

以上就是利用Worker对象实现多线程的整个过程,在Worker执行过程中,打开浏览器控制台的Memory,你可以看到Javascript VM instance(虚拟机)会多出一个线程;

回顾一下,这种方法是否似曾相识?没错,跟接口的做法殊途同归,将Worker当成接口,就好理解了,所以这是浏览器给前端的“接口”,上面的第一种做法就是这样得来的;

好了,Worker的使用就讲到这里,快去试试吧

微信公众号
微信公众号:
  • 前端全栈之路(微信群)
前端QQ交流群
前端QQ交流群:
  • 794324979
  • 734802480(已满)

更多文章

栏目文章


Copyright © 2014-2023 seozhijia.net 版权所有-粤ICP备13087626号-4