ParallelJS:优雅的Web Worker解决方案
ParallelJS为使用Web Worker时可能出现的问题提供了一种优雅的解决方案,它提供了一个具有便捷抽象和辅助工具的实用API。HTML5引入的Worker接口允许创建具有较长运行时间和高计算量需求的函数,这些函数可以同时使用以提高网站响应速度。ParallelJS允许对JavaScript代码进行并行化处理,利用同时多线程 (SMT) 更有效地使用现代CPU。ParallelJS库提供诸如spawn
、map
和reduce
等方法,分别用于并行执行计算、处理数据和聚合碎片化结果。
HTML5带来的一个最酷的新可能性是Web Workers API的Worker接口。在此之前,我们不得不采用一些技巧来向用户展示响应迅速的网站。Worker接口允许我们创建具有较长运行时间和高计算量需求的函数。此外,Worker实例可以同时使用,使我们能够根据需要生成任意数量的这些工作器。在本文中,我将讨论为什么多线程很重要,以及如何使用ParallelJS在JavaScript中实现它。
为什么需要多线程?
这是一个合理的问题。从历史上看,生成线程的能力提供了一种优雅的方式来划分进程中的工作。操作系统负责调度每个线程的可用时间,这样优先级更高、工作量更大的线程将优先于低优先级的空闲线程。在过去的几年里,同时多线程 (SMT) 已经成为访问现代CPU计算能力的关键。原因很简单:摩尔定律在每单位面积晶体管数量方面仍然有效。然而,由于多种原因,频率缩放不得不停止。因此,必须以其他方式使用可用的晶体管。人们决定,架构改进(例如SIMD)和多核代表最佳选择。
为了使用SMT,我们需要编写并行代码,即为获得单个结果而并行运行的代码。我们通常需要考虑特殊的算法,因为大多数顺序代码要么很难并行化,要么效率非常低。原因在于Amdahl定律,该定律指出加速比S由下式给出:
其中N是并行工作器的数量(例如处理器、核心或线程),P是并行部分。将来可能会使用更多依赖于并行算法的多核架构。在高性能计算领域,GPU系统和特殊架构(例如英特尔至强Phi)代表了此类平台。最后,我们应该区分一般的并发应用程序或算法和并行执行。并行性是(可能相关的)计算的同时执行。相反,并发是独立执行进程的组合。
JavaScript中的多线程
在JavaScript中,我们已经知道如何编写并发程序,即使用回调函数。现在可以将此知识转移到创建并行程序中!根据其自身的结构,JavaScript是在由事件循环(通常遵循反应器模式)调解的单个线程中执行的。例如,这为我们处理对(外部)资源的异步请求提供了一些很好的抽象。它还保证先前定义的回调始终在相同的执行线程中触发。没有与线程相关的跨线程异常、竞争条件或其他问题。但是,这并没有让我们更接近JavaScript中的SMT。随着Worker接口的引入,已经找到了一个优雅的解决方案。从主应用程序的角度来看,Web Worker中的代码应被视为并发运行的任务。通信也是以这种方式进行的。我们使用消息API,该API也可用于从包含的网站到托管页面的通信。例如,以下代码通过向发起者发送消息来响应传入的消息。
window.addEventListener('message', function (event) { event.source.postMessage('Howdy Cowboy!', event.origin); }, false);
理论上,Web Worker也可以生成另一个Web Worker。但是,实际上大多数浏览器禁止这样做。因此,Web Worker之间通信的唯一方法是通过主应用程序。通过消息进行的通信是并发进行的,因此只有异步(非阻塞)通信。起初,这在编程中可能很奇怪,但它带来了许多优点。最重要的是,我们的代码应该没有竞争条件!让我们来看一个使用两个参数表示序列的开始和结束来在后台计算素数序列的简单示例。首先,我们创建一个名为prime.js的文件,其中包含以下内容:
onmessage = function (event) { var arguments = JSON.parse(event.data); run(arguments.start, arguments.end); }; function run (start, end) { var n = start; while (n < end) { var k = Math.sqrt(n); var found = false; for (var i = 2; !found && i <= k; i++) { found = n % i === 0; } if (!found) { postMessage(n.toString()); } n++; } }
现在,我们只需要在主应用程序中使用以下代码来启动后台工作器即可。
if (typeof Worker !== 'undefined') { var w = new Worker('prime.js'); w.onmessage = function(event) { console.log(event); }; var args = { start : 100, end : 10000 }; w.postMessage(JSON.stringify(args)); }
相当多的工作。尤其令人讨厌的是使用另一个文件。这产生了很好的分离,但对于较小的任务似乎完全是多余的。幸运的是,有一种解决方法。考虑以下代码:
var fs = (function () { /* code for the worker */ }).toString(); var blob = new Blob( [fs.substr(13, fs.length - 14)], { type: 'text/javascript' } ); var url = window.URL.createObjectURL(blob); var worker = new Worker(url); // Now setup communication and rest as before
当然,我们可能希望有一个比这样的幻数(13和14)更好的解决方案,并且根据浏览器,必须使用Blob和createObjectURL的回退。如果您不是JavaScript专家,fs.substr(13, fs.length - 14)的作用是提取函数体。我们通过将函数声明转换为字符串(使用toString()调用)并删除函数本身的签名来做到这一点。
ParallelJS能帮上忙吗?
这就是ParallelJS发挥作用的地方。它为一些便利以及Web Worker提供了一个不错的API。它包括许多辅助工具和非常有用的抽象。我们首先提供一些要处理的数据。
var p = new Parallel([1, 2, 3, 4, 5]); console.log(p.data);
data字段产生提供的数组。还没有调用任何“并行”操作。但是,实例p包含一组方法,例如spawn,它将创建一个新的Web Worker。它返回一个Promise,这使得使用结果变得轻而易举。
window.addEventListener('message', function (event) { event.source.postMessage('Howdy Cowboy!', event.origin); }, false);
上面代码的问题是计算不会真正并行。我们只创建一个单个后台工作器,它一次性处理整个数据数组。只有在处理完整个数组后,我们才能获得结果。更好的解决方案是使用Parallel实例的map函数。
onmessage = function (event) { var arguments = JSON.parse(event.data); run(arguments.start, arguments.end); }; function run (start, end) { var n = start; while (n < end) { var k = Math.sqrt(n); var found = false; for (var i = 2; !found && i <= k; i++) { found = n % i === 0; } if (!found) { postMessage(n.toString()); } n++; } }
在前面的示例中,核心非常简单,可能过于简单。在一个真实的示例中,将涉及许多操作和函数。我们可以使用require函数包含引入的函数。
if (typeof Worker !== 'undefined') { var w = new Worker('prime.js'); w.onmessage = function(event) { console.log(event); }; var args = { start : 100, end : 10000 }; w.postMessage(JSON.stringify(args)); }
reduce函数有助于将碎片化的结果聚合到单个结果中。它提供了一个方便的抽象,用于收集子结果并在知道所有子结果后执行某些操作。
结论
ParallelJS为我们提供了一种优雅的方式来规避使用Web Worker时可能出现的问题。此外,我们获得了一个包含一些有用抽象和辅助工具的不错的API。将来可以集成进一步的改进。除了能够在JavaScript中使用SMT之外,我们可能还想使用矢量化功能。如果支持,SIMD.js似乎是一种可行的方法。在某些(希望不会太遥远的)将来,使用GPU进行计算也可能是一个有效的选项。在Node.js中存在CUDA(一种并行计算架构)的包装器,但是仍然无法执行原始JavaScript代码。在那之前,ParallelJS是我们充分利用多核CPU处理长时间运行计算的最佳选择。你呢?你如何使用JavaScript释放现代硬件的强大功能?
关于使用ParallelJS的并行JavaScript的常见问题解答 (FAQ)
ParallelJS是一个JavaScript库,允许您通过利用多核处理器来并行化数据处理。它的工作原理是创建一个新的Parallel对象并将一个数据数组传递给它。然后可以使用.map()
方法并行处理此数据,该方法将指定的函数应用于数组中的每个项目。然后在新的数组中返回结果。
可以使用npm(Node.js包管理器)安装ParallelJS。只需在终端中运行命令“npm install paralleljs”。安装完成后,您可以使用“var Parallel = require('paralleljs');”在您的JavaScript文件中引用它。
ParallelJS允许您充分利用多核处理器进行数据处理任务。这可以大大加快大型数据集的处理时间。它还提供了一个简单直观的API,使并行化代码变得容易。
是的,ParallelJS可以在浏览器中使用。您可以使用脚本标签和ParallelJS文件的URL将其包含在HTML文件中。包含后,您可以像在Node.js中一样使用Parallel对象。
.map()
方法?ParallelJS中的.map()
方法用于将函数应用于数据数组中的每个项目。该函数作为字符串传递给.map()
方法。然后在新的数组中返回结果。例如,“var p = new Parallel([1, 2, 3]); p.map('function(n) { return n * 2; }');”将返回一个值为[2, 4, 6]的新数组。
.reduce()
方法是什么?ParallelJS中的.reduce()
方法用于使用指定的函数将数据数组减少为单个值。该函数作为字符串传递给.reduce()
方法。例如,“var p = new Parallel([1, 2, 3]); p.reduce('function(a, b) { return a b; }');”将返回值6。
是的,ParallelJS中的方法可以链接在一起。例如,您可以使用.map()
方法处理数据,然后使用.reduce()
方法将结果组合成单个值。
可以使用.catch()
方法处理ParallelJS中的错误。此方法接受一个函数,如果在处理过程中发生错误,则会调用该函数。错误对象将传递给此函数。
是的,ParallelJS可以与其他JavaScript库一起使用。但是,您需要确保使用.require()
方法将库包含在worker上下文中。
虽然ParallelJS可以大大加快大型数据集的处理时间,但它可能并非所有任务的最佳选择。对于小型数据集,创建worker和传输数据的开销可能超过并行化的益处。最好使用您的具体用例测试ParallelJS,以查看它是否提供了性能优势。
以上是并联JavaScript的详细内容。更多信息请关注PHP中文网其他相关文章!