使用让 JavaScript 安全且并发的 Web Workers
tk2557
7年前
<p>Web Workers 提供了一种在浏览器单线程执行外运行 JS 代码的方式。单线程处理了包括页面内容展示,通过键盘、鼠标点击和其他装置的用户交互,以及对于 AJAX 请求响应的一些请求。</p> <p>事件处理和 AJAX 请求是异步发生的,可以作为一种通用浏览器展示代码路径之外的运行代码的方式,但是它们仍然在这一单线程内运行,并且必须很快完成。</p> <p>否则,浏览器中的交互会失去作用。</p> <p>Web Workers 允许在一个单独的线程运行 JS 代码,完全独立于浏览器线程及其常规活动。</p> <p>近年来,关于 Web Workers 有什么用的问题有许多争论。现如今 CPU 的速度非常快,几乎每个人的个人电脑刚买来打开就拥有几个 G 的内存。类似地,移动设备也已经达到了台式机的处理器速度和内存大小。</p> <p>那些或许曾经被认为是“计算密集型”的应用程序现在被认为没那么糟糕了。</p> <p>所谓的 <strong>这不是一片空白</strong> <strong>是什么意思?</strong></p> <p>但是很多次当我们决定如何高效地执行代码时,在开发环境中测试,我们只考虑了一个应用程序的执行。在现实生活系统中,一个用户的手中会同时执行许多程序。</p> <p>所以单独运行的应用或许没有必要使用 worker 线程,对于大范围的用户来说,或许才有必要使用 worker 线程提供最好的体验。</p> <p>启动一个新的 worker 就像是规定一个包含了 JS 代码的文件一样简单:</p> <pre> <code class="language-javascript">new Worker(‘worker-script.js’)</code></pre> <p>一旦创建了 worker ,它便独立于主浏览器线程单独运行,并执行脚本中赋予它的任何代码。只是浏览器看上去和存放专门脚本文件的 HTML 页面是有关联的。</p> <p>数据是通过 JS 代码中两个互补的特性实现在 Workers 和主 Javascript 线程中的传递的:</p> <ul> <li> <p>发送端的 postMessage() 方法</p> </li> <li> <p>接收端的 “message” 事件处理器</p> </li> </ul> <p>message 事件处理器像其他的处理器一样,接收到一个事件参数,这个事件有一个 “data” 属性,拥有从另一端传来的数据。</p> <p>这个可以成为一个双向的沟通:在主线程那边的代码可以调用 postMessage() 方法发送信息到 worker ,然后 worker 可以使用环境中全局适用的 postMessage() 方法的某种实现将信息返回给主线程。</p> <p>在 Web Worker 中的一个简单流程将会是这样的:在页面的 HTML 中,一条信息发送到 worker ,然后该页面等待一个响应:</p> <p><img src="https://simg.open-open.com/show/b632385291346cf6dc5689b1800e3334.png"></p> <p>worker 代码等待一条信息:</p> <p><img src="https://simg.open-open.com/show/644b1487ff1d9ea1b607e9c354083016.png"></p> <p>上面的代码将会在控制台打印输出:</p> <pre> <code class="language-javascript">Message posted from webworker: Worker running Message posted from webworker: Worker received data: {“data”:”123456789"}</code></pre> <p>Worker 期望为长连接,而无须反复重启</p> <p>在 Worker 的生命周期里,浏览器与 Worker 会发送与接收多条消息。</p> <p>Web Worker 的实现从以下两方面确保了线程安全:</p> <ul> <li> <p>为 Worker 线程创建唯一,相互独立的全局环境,而与浏览器的相隔离</p> </li> <li> <p>主从线程调用 postMessage() 交换的数据通过拷贝进行</p> </li> </ul> <p>每一个 Worker 线程的全局环境都是有别与浏览器页面的 JavaScript 环境的,并且是唯一的。Worker 不能访问浏览器中的 JavaScript 环境的任何数据,连 DOM、windows 对象、document 对象都不例外。</p> <p>Worker 有属于它们自己版本的东西,像将消息记录到开发人员的控制台的 ‘console’ 对象,以及发起 AJAX 请求的 XMLHttpRequest 对象。但除此之外,在 Worker 中运行的 Javascript 代码应该是自包含的;来自于 Worker 线程的任何输出,主窗口(main window)希望其都必须通过 postMessage() 函数作为数据返回。</p> <p>此外,任何数据通过 postMessage() 在传递之前被复制(传递副本),因此在主窗口线程中修改数据并不会导致 Worker 线程中的数据变更。这为 main 线程和 Worker 线程之间传递的数据的冲突并发更改提供了固有的保护。</p> <h3>web workers 用例</h3> <p>典型的 web worker 例子就是任何 task 任务在执行过程中可能会变得非常昂贵,要么消耗大量的 CPU 时间,要么花费大量的时钟时间(计算机调度的时钟)来访问数据。</p> <p>一些可能会用到 web workers 的用例:</p> <ul> <li> <p>预取 和/或 缓存数据供后面使用</p> </li> <li> <p>轮询和处理来自web服务器的数据</p> </li> <li> <p>处理和显示大数据集(think genomics)</p> </li> <li> <p>与游戏中的动作有关的计算</p> </li> <li> <p>图像处理和过滤</p> </li> <li> <p>处理文本数据(代码语法,拼写检查,字数计算)</p> </li> </ul> <p>CPU 时间(片)是一个简单的用例,但是对资源的网络访问也很重要。许多时候,网络上的网络通信可以以毫秒为单位执行,但有时网络资源变得不可用,直到网络恢复或者请求超时(需要1-2分钟才能清除)。</p> <p>即使一些代码在开发环境中被隔离测试时可能不会耗费过长运行时间,但当多个事物可以同时运行时,它可能会成为在用户环境中运行的问题。</p> <p>下面的例子演示了 web workers 可用的两个方法。</p> <h3>Demo:游戏碰撞检测</h3> <p>(注意:这是一个很长的例子)</p> <p>现在基于 HTML5 的游戏在网络浏览器中随处可见。游戏的一个中心方面是游戏环境中各个部分之间的计算运动和交互。有些游戏的移动部分相对较少,而且很容易将其动画化( <a href="/misc/goto?guid=4959757650371794989" rel="nofollow,noindex">超级马里奥模拟器</a> ,还有吗?)。但是让我们考虑一个更复杂的例子。</p> <p>例子涉及到大量的彩色球在矩形边界上跳跃(反弹)。目标就是让这些球保持在游戏的边缘,并且还可以探测球之间的碰撞并使它们彼此反弹。</p> <p>边界检测相对简单且执行速度快,但是碰撞检测在计算上的要求就更高了,因为它大致按球的数量的平方数增长:有“n” 个球,每一个球都要和其它球进行比较,看它们的路径是否相交,是否需要被反弹(结果是 n * n,或者 n 的平方的比较)。</p> <p>因此有 50 个球,需要进行 2500 次检查;有 100 个球,需要进行 10000 次检查(实际上它略少于这个数量的一半:如果你检查球 n 对球 m,你不需要稍后检查球 m 对球 n,但是,这里面仍然需要大量的计算)。</p> <p> </p> <p>来自:https://www.oschina.net/translate/sing-webworkers-for-safe-concurrent-javascript</p> <p> </p>