现在,对于使用计算机上闲置的多核处理器来说,这听起来是不是太受限了? html5 有望改变这一切。
javascript 的单线程模型web workers 生活在一个无法访问 dom 的受限世界中,因为 dom 不是线程安全的。
一种思想流派认为 javascript 的单线程本质是一种简化,但另一种流派则将其视为一种限制。后一组有一个非常好的观点,特别是当现代 web 应用程序大量使用 javascript 来处理 ui 事件、查询或轮询服务器端 api、处理大量数据以及根据服务器响应操作 dom 时。 p>
能够在单个控制线程中执行如此多的操作,同时保持响应式 ui 通常是一项艰巨的任务,它迫使开发人员诉诸黑客和解决方法(例如使用 settimeout(), setinterval(),或者使用xmlhttprequest和dom事件)来实现并发。然而,值得注意的是,这些技术肯定提供了一种异步调用的方法,但非阻塞并不一定意味着并发。 john resig 在他的博客上解释了为什么不能并行运行任何东西。
局限性如果您使用 javascript 有相当长的时间,您很可能遇到过以下烦人的对话框,指出某些脚本执行时间过长。是的,几乎每次您的页面停止响应时,原因都可以归因于某些 javascript 代码。
以下是浏览器在执行脚本时可能会挂起的一些原因:
过多的 dom 操作:dom 操作可能是 javascript 中成本最高的操作。因此,大量 dom 操作操作使您的脚本成为重构的良好候选者。永无休止的循环:扫描代码中是否存在复杂的嵌套循环永远不会有什么坏处。这些往往会做比实际需要的更多的工作。也许您可以找到提供相同功能的不同解决方案。将两者结合起来:当存在更优雅的解决方案(例如使用 documentfragment)时,我们能做的最糟糕的事情就是在循环中重复更新 dom。网络工作者来救援...非阻塞并不一定意味着并发...
感谢 html5 和 web workers,您现在可以生成一个新线程——提供真正的异步。新工作线程可以在主线程处理 ui 事件时在后台运行,即使工作线程正忙于处理大量数据。例如,工作人员可以处理大型 json 结构以提取有价值的信息以在 ui 中显示。但我的废话已经够多了;让我们看看一些实际的代码。
创建工作人员 通常,与 web worker 相关的代码驻留在单独的 javascript 文件中。父线程通过在 worker 构造函数中指定脚本文件的 uri 来创建一个新的 worker,该 worker 会异步加载并执行 javascript 文件。
var primeworker = new worker('prime.js');
启动一个工作线程 要启动一个工作线程,父线程会向该工作线程发送一条消息,如下所示:
var current = $('#prime').attr('value');primeworker.postmessage(current);
父页面可以使用 postmessage api 与工作人员通信,该 api 也用于跨源消息传递。除了向工作程序发送原始数据类型之外,postmessage api 还支持传递 json 结构。但是,您不能传递函数,因为它们可能包含对底层 dom 的引用。
父线程和工作线程有自己独立的空间;来回传递的消息是复制的,而不是共享的。
在幕后,这些消息在工作线程处序列化,然后在接收端反序列化。因此,不鼓励向工作线程发送大量数据。
父线程还可以注册一个回调来侦听工作线程在执行其任务后发回的任何消息。这允许父线程在工作线程发挥作用后采取必要的操作(例如更新 dom)。看看这段代码:
primeworker.addeventlistener('message', function(event){ console.log('receiving from worker: '+event.data); $('#prime').html( event.data );});
event 对象包含两个重要属性:
target:用于标识发送消息的worker;主要在多工作人员环境中有用。data:worker 发送回其父线程的消息。worker 本身包含在 prime.js 中,并注册从其父级接收的 message 事件。它还使用相同的 postmessage api 与父线程通信。
self.addeventlistener('message', function(event){ var currprime = event.data, nextprime; setinterval( function(){ nextprime = getnextprime(currprime); postmessage(nextprime); currprime = nextprime; }, 500);});
网络工作者生活在一个受限且线程安全的环境中。
在此示例中,我们只需找到下一个最大素数,然后重复将结果发送回父线程,父线程又用新值更新 ui。在工作者上下文中, self 和 this 均指全局范围。 worker 可以为 message 事件添加事件侦听器,也可以定义 onmessage 处理程序来侦听父线程发送的任何消息。
查找下一个素数的任务显然不是工作人员的理想用例,但在这里选择它是为了演示传递消息的概念。随后,我们确实探索了使用 web worker 真正能带来好处的可能且实际的用例。
终止员工工人是资源密集型的;它们是操作系统级线程。因此,您不想创建大量工作线程,并且应该在 web worker 完成工作后终止它。工人可以终止自己,如下所示:
self.close();
或者父线程可以终止工作线程:
primeworker.terminate();
安全和限制在工作脚本中,我们无法访问许多重要的 javascript 对象,例如 document、window、console、parent,最重要的是无法访问 dom。没有 dom 访问权限并且无法更新页面确实听起来限制太多,但这是一个重要的安全设计决策。想象一下,如果多个线程尝试更新同一元素,可能会造成严重破坏。因此,网络工作者生活在一个受限且线程安全的环境中。
话虽如此,您仍然可以使用worker来处理数据并将结果返回到主线程,然后主线程可以更新dom。尽管他们被拒绝访问一些非常重要的 javascript 对象,但工作人员可以使用一些函数,例如 settimeout()/cleartimeout()、setinterval()/clearinterval()、navigator 等。还可以在工作器内使用 xmlhttprequest 和 localstorage 对象。
同源限制 在工作者上下文中, self 和 this 均指全局范围。
为了与服务器通信,工作人员必须遵循同源策略。例如,托管在 http://www.example.com/ 上的脚本无法访问 https://www.example.com/ 上的脚本。即使主机名相同,同源策略也规定协议也必须相同。通常,这不是问题。您很可能正在编写工作程序、客户端,并从同一域为它们提供服务,但了解限制总是有用的。
google chrome 的本地访问问题 google chrome 对本地访问工作程序设置了限制,因此您将无法在本地设置上运行这些示例。如果您想使用 chrome,则必须在某个服务器上托管这些文件,或者在从命令行启动 chrome 时使用 --allow-file-access-from-files 标志。对于 os x,按如下方式启动 chrome:
$ /applications/google\ chrome.app/contents/macos/google\ chrome --allow-file-access-from-files
但是,不建议在生产环境中使用此标志。因此,最好的选择是将这些文件托管在网络服务器上,并在任何支持的浏览器中测试您的网络工作人员。
调试 worker 和错误处理无法访问 console 使得这有点不简单,但是借助 chrome 开发工具,人们可以像调试任何其他 javascript 代码一样调试工作代码。
要处理 web worker 抛出的任何错误,您可以侦听 error 事件,该事件会填充 errorevent 对象。您可以检查该对象以了解错误的详细原因。
primeworker.addeventlistener('error', function(error){ console.log(' error caused by worker: '+error.filename + ' at line number: '+error.lineno + ' detailed message: '+error.message);});
多个工作线程虽然多个工作线程在它们之间划分工作是很常见的,但需要注意的是。官方规范指定这些工作人员相对重量级,预计将是在后台运行的长期脚本。 web worker 不适合大量使用,因为它们的启动性能成本和每个实例的内存成本都很高。
共享工作人员简介 该规范概述了两种类型的工作人员:专用工作人员和共享工作人员。到目前为止,我们已经看到了敬业工人的例子。它们直接链接到其创建者脚本/页面,因为它们与创建它们的脚本/页面具有一对一的关系。另一方面,共享工作人员可以在同一个来源的所有页面之间共享(即:同一来源的所有页面或脚本都可以与共享工作人员通信)。
要创建共享工作线程,只需将脚本的 url 或工作线程的名称传递给 sharedworker 构造函数即可。
共享工作程序使用方式的主要区别在于,它们与 port 相关联,以跟踪访问它们的父脚本。
以下代码片段创建一个共享工作线程,注册回调以监听该工作线程发布的任何消息,并将消息发布到共享工作线程:
var sharedworker = new sharedworker('findprime.js');sharedworker.port.onmessage = function(event){ ...}sharedworker.port.postmessage('data you want to send');
类似地,工作人员可以侦听 connect 事件,当新客户端尝试连接到工作人员时会收到该事件,然后相应地向其发送消息。
onconnect = function(event) { // event.source contains the reference to the client's port var clientport = event.source; // listen for any messages send my this client clientport.onmessage = function(event) { // event.data contains the message send by client var data = event.data; .... // post data after processing clientport.postmessage('processed data'); }};
由于它们的共享性质,您可以在同一应用程序的不同选项卡中维护相同的状态,因为不同选项卡中的两个页面使用相同的共享工作脚本来维护和报告状态。有关共享工作人员的更多详细信息,我鼓励您阅读规范。
实际用例 web worker 不适合大量使用,因为它们的启动性能成本很高,每个实例的内存成本也很高。
现实生活中的场景可能是,您被迫处理同步第三方 api,该 api 强制主线程在继续执行下一条语句之前等待结果。在这种情况下,您可以将此任务委托给新生成的工作线程,以利用异步功能为您带来好处。
web 工作人员还擅长轮询情况,在这种情况下,您可以在后台连续轮询目标,并在一些新数据到达时将消息发布到主线程。
您可能还需要处理服务器返回的大量数据。传统上,处理大量数据会对应用程序的响应能力产生负面影响,从而使用户体验变得不可接受。更优雅的解决方案是将处理工作分配给多个工作人员来处理数据的非重叠部分。
其他用例可能是在多个网络工作人员的帮助下分析视频或音频源,每个工作人员都处理问题的预定义部分。
结论想象一下在单线程环境中与多个线程相关的强大功能。
与 html5 规范中的许多内容一样,web worker 规范也在不断发展。如果您打算成为网络工作者,那么看看规范不会有什么坏处。
对于使用当前版本的 chrome、safari 和 firefox 的专业工作人员来说,跨浏览器支持相当不错。即使是 ie 也没有落后太多,ie10 占据了主导地位。但是,仅当前版本的 chrome 和 safari 支持共享工作线程。令人惊讶的是,android 4.0 中提供的最新版本的 android 浏览器不支持 web worker,尽管在 2.1 版本中支持了 web worker。 apple 还从 ios 5.0 开始提供了 web worker 支持。
想象一下在单线程环境中与多线程相关的强大功能。可能性是无限的!
以上就是从 web worker 开始的详细内容。