• 처리 시간이 오래 걸리는 스크립트의 실행 동안에는 UI(DOM, 사용자 이벤트 처리) 작업을 처리할 수 없음
▶ Worker
• 메인 스레드(UI)와 독립되어 백그라운드 프로세스로 처리되는 스크립트
• 처리 시간이 오래 걸리는 작업에 사용하면 메인 스레드와 같이 병행하여 동작
• 워커를 이용한 병렬 처리는 멀티 코어에서 최적화된 성능을 발휘
• UI 처리와 비즈니스 로직의 분리
▶ Worker의 종류
• 전용(dedicated) 워커
- 워커 객체와 백그라운드 프로세스가 일대일로 대응
• 공유(shared) 워커
- 다수의 워커 객체에 의해 공유되는 백그라운드 프로세스(데이터 공유)
▶ UI 스레드와 Worker
• 워커는 UI 스레드에서 생성되면 독립적인 프로세스로 동작
• UI 스레드가 가지고 있는 DOM 객체(window, document 등) 접근 불가
• 워커에서 처리한 결과를 화면에 반영할 때에는 MessageEvent를 이용하여 비동기 메시지 전달(UI와 워커, 워커와 워커 간 메시지 전달)
▶ Worker 작성
• 독립된 js 파일로 작성
• 일반 자바스크립트와 다른 환경이기 때문에 전용의 속성과 메서드 사용
• Worker 내에서 사용하는 주요 메서드
- importScripts(url, [url]...) : 워커에서 사용할 외부 스크립트 로딩
- postMessage(message, [ports]) : 워커를 생성한 곳으로 메시지 전송
• Worker 내에서 사용하는 주요 이벤트
- message : 워커를 생성한 곳으로부터 메시지 수신 시 발생
▶ Worker 생성
• 생성자에 js 파일(워커)을 지정하여 생성
- new Worker("myworker.js");
• 워커가 생성되는 순간 바로 워커의 스크립트가 실행
▶ Worker 인터페이스
• 주요 메서드
- postMessage(message, [ports]) : 워커에 메시지 전송
- terminate() : 워커를 강제로 종료
• 주요 이벤트
- message : 워커의 메시지 수신 시 발생
▶ Worker를 이용한 프로그래밍 절차
• js 파일로 워커 작성
- message 이벤트 지정(처리할 작업)
- 처리할 작업이 끝나면 postMessage() 메서드로 결과 송신
• 메인 스레드에서 워커 생성
- var worker = new Worker("myworker.js);
• 워커에 메시지 전달
- message 이벤트 지정(워커에서 보내는 결과 수신)
- postMessage() 메서드로 워커에 메시지 전달(작업 지시)
▶ 공유 워커
• 다수의 워커에게 공유되는 백그라운드 프로세스
• 생성
- new SharedWorker(worker, workerName);
• 동일한 워커를 동일한 이름으로 생성하면 워커 프로세스는 하나만 생기고 두 워커 객체는 하나의 프로세서를 공유함(브라우저의 다른 윈도우에서도 가능)
▶ SharedWorker 인터페이스
• 주요 속성
- port : 공유 워커와 통신할 수 있는 MessagePort 객체
• 주요 메서드
- Worker와 동일(postMessage() 메서드는 port의 메서드 사용)
• 주요 이벤트
- connect : SharedWorker 생성 시 발생
▶ 공유 워커에서 구현할 내용
• connect 이벤트 지정
- SharedWorker 생성 시 호출 되며 인자 값인 MessageEvent 객체의 ports 속성으로 클라이언트와 메시지 송수신
- 포트의 postMessage() 메서드를 이용하여 클라이언트에 메시지 송신
- 포트의 message 이벤트를 이용하여 클라이언트로부터의 메시지 수신
▶ 클라이언트에서 구현할 내용
• SharedWorker 생성
• SharedWorker 객체의 port 속성(MessagePort 타입)을 이용하여 공유 워커와 메시지 송수신
- postMessage() 메서드를 이용하여 공유 워커에 메시지 송신
- message 이벤트를 이용하여 공유 워커로부터의 메시지 수신
[출처] html5_Web Workers|작성자 두기
=======================
=======================
=======================
출처: http://egloos.zum.com/jaures/v/2316325
JavaScript는 single thread 이다. 즉, 어떤 작업이 진행중인 상황에서 다른 작업을 동시에 실행시킬 수 없다는 의미인데 이러한 한계로 인해 복잡한 작업이 실행되어야 하는 경우에는 실행속도가 현저하게 떨어질 수밖에 없다.
Firefox 3.5 (이전 3.1 버전)에서는 이를 극복하기 위한 Web Workers thread가 최초로 도입 되었는데, 간단히 얘기하자면 Web Workers는 백그라운드에서 thread를 생성해 현재 페이지에서 다른 작업이 진행중이더라도 별도의 thread가 생성되어 single thread의 한계를 극복할 수 있도록 도와준다.
A web worker is a single JavaScript file loaded and executed in the background. There are two types:
Dedicated workers: these are linked to their creator (the script which loaded the worker).
Shared workers: the allow any script from the same domain (somesite.com) to communicate with the worker.
Today, we’re looking at dedicated web workers…
Creating a Dedicated Web Worker
To create a dedicated web worker, you pass a JavaScript file name to a new instance of the Worker object:
var worker = new Worker("thread1.js");
Communicating With a Dedicated Web Worker
Since the web worker cannot access the DOM or execute functions within the page’s script, all communication is handled through an event interface. The web page script passes a single data argument via the postMessage() method and receives one back via an onmessage event handler, e.g.
pagescript.js:
var worker = new Worker("thread1.js"); // receive messages from web worker worker.onmessage = function(e) { alert(e.data); }; // send message to web worker worker.postMessage("Jennifer");
The web worker script receives and sends data via it’s own onmessage event handler and postMessage() method accordingly:
The message data can be a string, number, boolean, array, object, null or undefined. Data is always passed by value and serialized then de-serialized during the communication process.
Handling Dedicated Web Worker Errors
Web worker code is unlikely to be perfect, and logic errors could be caused by the data passed by the page script. Fortunately, errors can be caught using an onerror event handler. The handler event is passed an object with 3 properties:
filename: the name of the script which caused the error;
lineno: the line number where the error occurred; and
웹 워커는 웹 컨텐츠를 위해서 백그라운드 스레드에서 스크립트를 실행할 간편한 방법을 제공합니다. 워커 스레드는 사용자 인터페이스(UI)를 방해하지 않고 작업을 수행할 수 있습니다. 또한 워커는 ( responseXML과 channel속성이 언제나 null이지만) XMLHttpRequest 를 사용하여 I/O작업을 수행할 수도 있습니다. 워커는 생성이 된 후에 생성자가 명시한 이벤트 핸들러로 메세지를 올려서 자신의 하위 작업(spawning task)에 메세지를 전달할 수 도 있습니다. 본 글에서 전용 워커와 공유 워커에 대하여 소개합니다.
A worker is an object created using a constructor (e.g. Worker()) that runs a named JavaScript file — this file contains the code that will run in the worker thread; workers run in another global context that is different from the current window. Thus, using the window shortcut to get the current global scope (instead of self) within a Worker will return an error.
The worker context is represented by a DedicatedWorkerGlobalScope object in the case of dedicated workers (standard workers that are utilized by a single script; shared workers use SharedWorkerGlobalScope). A dedicated worker is only accessible from the script that first spawned it, whereas shared workers can be accessed from multiple scripts.
You can run whatever code you like inside the worker thread, with some exceptions. For example, you can't directly manipulate the DOM from inside a worker, or use some default methods and properties of the window object. But you can use a large number of items available under window, including WebSockets, and data storage mechanisms like IndexedDB and the Firefox OS-only Data Store API. See Functions and classes available to workers for more details.
Data is sent between workers and the main thread via a system of messages — both sides send their messages using the postMessage() method, and respond to messages via the onmessage event handler (the message is contained within the Messageevent's data attribute.) The data is copied rather than shared.
Workers may, in turn, spawn new workers, as long as those workers are hosted within the same origin as the parent page. In addition, workers may use XMLHttpRequest for network I/O, with the exception that the responseXML and channel attributes on XMLHttpRequest always return null.
As mentioned above, a dedicated worker is only accessible by the script that called it. In this section we'll discuss the JavaScript found in our Basic dedicated worker example (run dedicated worker): This allows you to enter two numbers to be multiplied together. The numbers are sent to a dedicated worker, multiplied together, and the result is returned to the page and displayed.
This example is rather trivial, but we decided to keep it simple while introducing you to basic worker concepts. More advanced details are covered later on in the article.
Worker feature detection
For slightly more controlled error handling and backwards compatibility, it is a good idea to wrap your worker accessing code in the following (main.js):
if (window.Worker) { ... }
Spawning a dedicated worker
Creating a new worker is simple. All you need to do is call the Worker() constructor, specifying the URI of a script to execute in the worker thread (main.js):
var myWorker = new Worker("worker.js");
Sending messages to and from a dedicated worker
The magic of workers happens via the postMessage() method and the onmessage event handler. When you want to send a message to the worker, you post messages to it like this (main.js):
first.onchange = function() { myWorker.postMessage([first.value,second.value]); console.log('Message posted to worker'); } second.onchange = function() { myWorker.postMessage([first.value,second.value]); console.log('Message posted to worker'); }
So here we have two <input> elements represented by the variables first and second; when the value of either is changed, myWorker.postMessage([first.value,second.value]) is used to send the value inside both to the worker, as an array. You can send pretty much anything you like in the message.
In the worker, we can respond when the message is received by writing an event handler block like this (worker.js):
onmessage = function(e) { console.log('Message received from main script'); var workerResult = 'Result: ' + (e.data[0] * e.data[1]); console.log('Posting message back to main script'); postMessage(workerResult); }
The onmessage handler allows us to run some code whenever a message is received, with the message itself being available in the message event's data attribute. Here we simply multiply together the two numbers then use postMessage() again, to post the result back to the main thread.
Back in the main thread, we use onmessage again, to respond to the message sent back from the worker:
myWorker.onmessage = function(e) { result.textContent = e.data; console.log('Message received from worker'); }
Here we grab the message event data and set it as the textContent of the result paragraph, so the user can see the result of the calculation.
Note: The URI passed as a parameter to the Worker constructor must obey the same-origin policy .
There is currently disagreement among browsers vendors on what URIs are of the same-origin; Gecko 10.0 (Firefox 10.0 / Thunderbird 10.0 / SeaMonkey 2.7) and later do allow data URIs and Internet Explorer 10 does not allow Blob URIs as a valid script for workers.
Note: Notice that onmessage and postMessage() need to be hung off the Worker object when used in the main script thread, but not when used in the worker. This is because, inside the worker, the worker is effectively the global scope.
If you need to immediately terminate a running worker from the main thread, you can do so by calling the worker's terminate method:
myWorker.terminate();
The worker thread is killed immediately without an opportunity to complete its operations or clean up after itself.
In the worker thread, workers may close themselves by calling their own close method:
close();
Handling errors
When a runtime error occurs in the worker, its onerror event handler is called. It receives an event named error which implements the ErrorEvent interface.
The event doesn't bubble and is cancelable; to prevent the default action from taking place, the worker can call the error event's preventDefault()method.
The error event has the following three fields that are of interest:
message
A human-readable error message.
filename
The name of the script file in which the error occurred.
lineno
The line number of the script file on which the error occurred.
Spawning subworkers
Workers may spawn more workers if they wish. So-called sub-workers must be hosted within the same origin as the parent page. Also, the URIs for subworkers are resolved relative to the parent worker's location rather than that of the owning page. This makes it easier for workers to keep track of where their dependencies are.
Importing scripts and libraries
Worker threads have access to a global function, importScripts(), which lets them import scripts. It accepts zero or more URIs as parameters to resources to import; all of the following examples are valid:
importScripts(); /* imports nothing */ importScripts('foo.js'); /* imports just "foo.js" */ importScripts('foo.js', 'bar.js'); /* imports two scripts */ importScripts('//example.com/hello.js'); /* You can import scripts from other origins */
The browser loads each listed script and executes it. Any global objects from each script may then be used by the worker. If the script can't be loaded, NETWORK_ERROR is thrown, and subsequent code will not be executed. Previously executed code (including code deferred using window.setTimeout()) will still be functional though. Function declarations after the importScripts()method are also kept, since these are always evaluated before the rest of the code.
Note: Scripts may be downloaded in any order, but will be executed in the order in which you pass the filenames into importScripts() . This is done synchronously; importScripts() does not return until all the scripts have been loaded and executed.
A shared worker is accessible by multiple scripts — even if they are being accessed by different windows, iframes or even workers. In this section we'll discuss the JavaScript found in our Basic shared worker example (run shared worker): This is very similar to the basic dedicated worker example, except that it has two functions available handled by different script files: multiplying two numbers, or squaring a number. Both scripts use the same worker to do the actual calculation required.
Here we'll concentrate on the differences between dedicated and shared workers. Note that in this example we have two HTML pages, each with JavaScript applied that uses the same single worker file.
Note: If SharedWorker can be accessed from several browsing contexts, all those browsing contexts must share the exact same origin (same protocol, host, and port).
Note: In Firefox, shared workers cannot be shared between documents loaded in private and non-private windows (bug 1177621).
Spawning a shared worker
Spawning a new worker is pretty much the same as with a dedicated worker, but with a different constructor name (see index.html and index2.html) — each one has to spin up the worker using code like the following:
var myWorker = new SharedWorker("worker.js");
One big difference is that with a shared worker you have to communicate via a port object — an explicit port is opened that the scripts can use to communicate with the worker (this is done implicitly in the case of dedicated workers).
The port connection needs to be started either implicitly by use of the onmessage event handler or explicitly with the start()method before any messages can be posted. Although the multiply.js and worker.js files in the demo currently call the start()method, those calls are not necessary since the onmessage event handler is being used. Calling start() is only needed if the message event is wired up via the addEventListener() method.
When using the start() method to open the port connection, it needs to be called from both the parent thread and the worker thread if two-way communication is needed.
myWorker.port.start(); // called in parent thread
port.start(); // called in worker thread, assuming the port variable references a port
Sending messages to and from a shared worker
Now messages can be sent to the worker as before, but the postMessage() method has to be invoked through the port object (again, you'll see similar constructs in both multiply.js and square.js):
squareNumber.onchange = function() { myWorker.port.postMessage([squareNumber.value,squareNumber.value]); console.log('Message posted to worker'); }
Now, on to the worker. There is a bit more complexity here as well (worker.js):
onconnect = function(e) { var port = e.ports[0]; port.onmessage = function(e) { var workerResult = 'Result: ' + (e.data[0] * e.data[1]); port.postMessage(workerResult); } }
First, we use an onconnect handler to fire code when a connection to the port happens (i.e. when the onmessage event handler in the parent thread is setup, or when the start() method is explicitly called in the parent thread).
We use the ports attribute of this event object to grab the port and store it in a variable.
Next, we add a message handler on the port to do the calculation and return the result to the main thread. Setting up this messagehandler in the worker thread also implicitly opens the port connection back to the parent thread, so the call to port.start() is not actually needed, as noted above.
Finally, back in the main script, we deal with the message (again, you'll see similar constructs in both multiply.js and square.js):
myWorker.port.onmessage = function(e) { result2.textContent = e.data; console.log('Message received from worker'); }
When a message comes back through the port from the worker, we check what result type it is, then insert the calculation result inside the appropriate result paragraph.
The Worker interface spawns real OS-level threads, and mindful programmers may be concerned that concurrency can cause “interesting” effects in your code if you aren't careful.
However, since web workers have carefully controlled communication points with other threads, it's actually very hard to cause concurrency problems. There's no access to non-threadsafe components or the DOM. And you have to pass specific data in and out of a thread through serialized objects. So you have to work really hard to cause problems in your code.
Workers are considered to have their own execution context, distinct from the document that created them. For this reasons they are, in general, not governed by the content security policy of the document (or parent worker) that created them. So for example, suppose a document is served with the following header:
Content-Security-Policy: script-src 'self'
Among other things, this will prevent any scripts it includes from using eval(). However, if the script constructs a worker, code running in the worker's context will be allowed to use eval().
To specify a content security policy for the worker, set a Content-Security-Policy response header for the request which delivered the worker script itself.
The exception to this is if the worker script's origin is a globally unique identifier (for example, if its URL has a scheme of data or blob). In this case, the worker does inherit the CSP of the document or worker than created it.
Transferring data to and from workers: further detailsEdit
Data passed between the main page and workers is copied, not shared. Objects are serialized as they're handed to the worker, and subsequently, de-serialized on the other end. The page and worker do not share the same instance, so the end result is that a duplicate is created on each end. Most browsers implement this feature as structured cloning.
To illustrate this, let's create for didactical purpose a function named emulateMessage(), which will simulate the behavior of a value that is cloned and not shared during the passage from a worker to the main page or vice versa:
function emulateMessage (vVal) { return eval("(" + JSON.stringify(vVal) + ")"); } // Tests // test #1 var example1 = new Number(3); console.log(typeof example1); // object console.log(typeof emulateMessage(example1)); // number // test #2 var example2 = true; console.log(typeof example2); // boolean console.log(typeof emulateMessage(example2)); // boolean // test #3 var example3 = new String("Hello World"); console.log(typeof example3); // object console.log(typeof emulateMessage(example3)); // string // test #4 var example4 = { "name": "John Smith", "age": 43 }; console.log(typeof example4); // object console.log(typeof emulateMessage(example4)); // object // test #5 function Animal (sType, nAge) { this.type = sType; this.age = nAge; } var example5 = new Animal("Cat", 3); alert(example5.constructor); // Animal alert(emulateMessage(example5).constructor); // Object
A value that is cloned and not shared is called message. As you will probably know by now, messages can be sent to and from the main thread by using postMessage(), and the message event's data attribute contains data passed back from the worker.
example.html: (the main page):
var myWorker = new Worker("my_task.js"); myWorker.onmessage = function (oEvent) { console.log("Worker said : " + oEvent.data); }; myWorker.postMessage("ali");
my_task.js (the worker):
postMessage("I\'m working before postMessage(\'ali\')."); onmessage = function (oEvent) { postMessage("Hi " + oEvent.data); };
The structured cloning algorithm can accept JSON and a few things that JSON can't — like circular references.
Passing data examples
Example #1: Create a generic "asynchronous eval()"
The following example shows how to use a worker in order to asynchronously execute any JavaScript code allowed in a worker, through eval() within the worker:
// Syntax: asyncEval(code[, listener]) var asyncEval = (function () { var aListeners = [], oParser = new Worker("data:text/javascript;charset=US-ASCII,onmessage%20%3D%20function%20%28oEvent%29%20%7B%0A%09postMessage%28%7B%0A%09%09%22id%22%3A%20oEvent.data.id%2C%0A%09%09%22evaluated%22%3A%20eval%28oEvent.data.code%29%0A%09%7D%29%3B%0A%7D"); oParser.onmessage = function (oEvent) { if (aListeners[oEvent.data.id]) { aListeners[oEvent.data.id](oEvent.data.evaluated); } delete aListeners[oEvent.data.id]; }; return function (sCode, fListener) { aListeners.push(fListener || null); oParser.postMessage({ "id": aListeners.length - 1, "code": sCode }); }; })();
The data URL is equivalent to a network request, with the following response:
Example #2: Advanced passing JSON Data and creating a switching system
If you have to pass some complex data and have to call many different functions both on the main page and in the Worker, you can create a system which groups everything together.
First, we create a QueryableWorker class that takes the
url
of the worker, a default listener, and an error handler, and this class is gonna keep track of a list of listeners and help us communicate wirh the worker:
function QueryableWorker(url, defaultListener, onError){ var instance = this, worker = new Worker(url), listeners = {}; this.defaultListener = defaultListener || function(){}; if (onError) {worker.onerror = onError;} this.postMessage = function(message){ worker.postMessage(message); } this.terminate = function(){ worker.terminate(); } }
Then we add the methods of adding/removing listeners:
Here we let the worker handle two simple operations for illuatration: getting the difference of two numbers and making an alert after three seconds. In order to acheieve that we first implement a sendQuery method which queries if the worker actually has the corresponding methods to do what we want.
/* This functions takes at least one argument, the method name we want to query. Then we can pass in the arguments that the method needs. */ this.sendQuery = function(){ if (arguments.length < 1){ throw new TypeError("QueryableWorker.sendQuery takes at least one argument"); return; } worker.postMessage({ "queryMethod": arguments[0], "queryArguments": Array.prototype.slice.call(arguments, 1) }); }
We finish QueryableWorker with the onmessage method. If the worker has the corresponding methods we queried, it should return the name of the corresponding listener and the arguments it needs, we just need to find it in listeners.:
Now onto the worker. First we need to have the methods to handle the two simple operations:
var queryableFunctions = { getDifference: function(a, b){ reply("printStuff", a - b); }, waitSomeTime: function(){ setTimeout(function(){ reply("doAlert", 3, "seconds"); }, 3000); } } function reply(){ if (arguments.length < 1) { throw new TypeError("reply - takes at least one argument"); return; } postMessage({ queryMethodListener: arguments[0], queryMethodArguments: Array.prototype.slice.call(arguments, 1) }); } /* This method is called when main page calls QueryWorker's postMessage method directly*/ function defaultReply(message){ // do something }
<!doctype html> <html> <head> <meta charset="UTF-8" /> <title>MDN Example - Queryable worker</title> <script type="text/javascript"> /* QueryableWorker instances methods: * sendQuery(queryable function name, argument to pass 1, argument to pass 2, etc. etc): calls a Worker's queryable function * postMessage(string or JSON Data): see Worker.prototype.postMessage() * terminate(): terminates the Worker * addListener(name, function): adds a listener * removeListener(name): removes a listener QueryableWorker instances properties: * defaultListener: the default listener executed only when the Worker calls the postMessage() function directly */ function QueryableWorker(url, defaultListener, onError){ var instance = this, worker = new Worker(url), listeners = {}; this.defaultListener = defaultListener || function(){}; if (onError) {worker.onerror = onError;} this.postMessage = function(message){ worker.postMessage(message); } this.terminate = function(){ worker.terminate(); } this.addListeners = function(name, listener){ listeners[name] = listener; } this.removeListeners = function(name){ delete listeners[name]; } worker.onmessage = function(event){ if (event.data instanceof Object && event.data.hasOwnProperty("queryMethodListener") && event.data.hasOwnProperty("queryMethodArguments")){ listeners[event.data.queryMethodListener].apply(instance, event.data.queryMethodArguments); } else { this.defaultListener.call(instance, event.data); } } } // your custom "queryable" worker var myTask = new QueryableWorker("my_task.js"); // your custom "listeners" myTask.addListener("printStuff", function (result) { document.getElementById("firstLink").parentNode.appendChild(document.createTextNode(" The difference is " + result + "!")); }); myTask.addListener("doAlert", function (time, unit) { alert("Worker waited for " + time + " " + unit + " :-)"); }); </script> </head> <body> <ul> <li><a id="firstLink" href="javascript:myTask.sendQuery('getDifference', 5, 3);">What is the difference between 5 and 3?</a></li> <li><a href="javascript:myTask.sendQuery('waitSomeTime');">Wait 3 seconds</a></li> <li><a href="javascript:myTask.terminate();">terminate() the Worker</a></li> </ul> </body> </html>
my_task.js (the worker):
var queryableFunctions = { // example #1: get the difference between two numbers: getDifference: function (nMinuend, nSubtrahend) { reply("printSomething", nMinuend - nSubtrahend); }, // example #2: wait three seconds waitSomeTime: function () { setTimeout(function() { reply("doAlert", 3, "seconds"); }, 3000); } }; // system functions function defaultReply (message) { // your default PUBLIC function executed only when main page calls the queryableWorker.postMessage() method directly // do something } function reply () { if (arguments.length < 1) { throw new TypeError("reply - not enough arguments"); return; } postMessage({ "queryMethodListener": arguments[0], "queryMethodArguments": Array.prototype.slice.call(arguments, 1) }); } onmessage = function (oEvent) { if (oEvent.data instanceof Object && oEvent.data.hasOwnProperty("queryMethod") && oEvent.data.hasOwnProperty("queryMethodArguments")) { queryableFunctions[oEvent.data.queryMethod].apply(self, oEvent.data.queryMethodArguments); } else { defaultReply(oEvent.data); } };
It is possible to switch the content of each mainpage -> worker and worker -> mainpage message. And the property names "queryMethod", "queryMethodListeners", "queryMethodArguments" can be anything as long as they are consistent in QueryableWorker and the worker.
Passing data by transferring ownership (transferable objects)
Google Chrome 17+ and Firefox 18+ contain an additional way to pass certain types of objects (transferable objects, that is objects implementing the Transferable interface) to or from a worker with high performance. Transferable objects are transferred from one context to another with a zero-copy operation, which results in a vast performance improvement when sending large data sets. Think of it as pass-by-reference if you're from the C/C++ world. However, unlike pass-by-reference, the 'version' from the calling context is no longer available once transferred. Its ownership is transferred to the new context. For example, when transferring an ArrayBuffer from your main app to a worker script, the original ArrayBuffer is cleared and no longer usable. Its content is (quite literally) transferred to the worker context.
// Create a 32MB "file" and fill it. var uInt8Array = new Uint8Array(1024*1024*32); // 32MB for (var i = 0; i < uInt8Array.length; ++i) { uInt8Array[i] = i; } worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);
Note: For more information on transferable objects, performance, and feature-detection for this method, read Transferable Objects: Lightning Fast! on HTML5 Rocks.
There is not an "official" way to embed the code of a worker within a web page, like <script> elements do for normal scripts. But a <script> element that does not have a src attribute and has a type attribute that does not identify an executable MIME type can be considered a data block element that JavaScript could use. "Data blocks" is a more general feature of HTML5 that can carry almost any textual data. So, a worker could be embedded in this way:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>MDN Example - Embedded worker</title> <script type="text/js-worker"> // This script WON'T be parsed by JS engines because its MIME type is text/js-worker. var myVar = "Hello World!"; // Rest of your worker code goes here. </script> <script type="text/javascript"> // This script WILL be parsed by JS engines because its MIME type is text/javascript. function pageLog (sMsg) { // Use a fragment: browser will only render/reflow once. var oFragm = document.createDocumentFragment(); oFragm.appendChild(document.createTextNode(sMsg)); oFragm.appendChild(document.createElement("br")); document.querySelector("#logDisplay").appendChild(oFragm); } </script> <script type="text/js-worker"> // This script WON'T be parsed by JS engines because its MIME type is text/js-worker. onmessage = function (oEvent) { postMessage(myVar); }; // Rest of your worker code goes here. </script> <script type="text/javascript"> // This script WILL be parsed by JS engines because its MIME type is text/javascript. // In the past...: // blob builder existed // ...but now we use Blob...: var blob = new Blob(Array.prototype.map.call(document.querySelectorAll("script[type=\"text\/js-worker\"]"), function (oScript) { return oScript.textContent; }),{type: "text/javascript"}); // Creating a new document.worker property containing all our "text/js-worker" scripts. document.worker = new Worker(window.URL.createObjectURL(blob)); document.worker.onmessage = function (oEvent) { pageLog("Received: " + oEvent.data); }; // Start the worker. window.onload = function() { document.worker.postMessage(""); }; </script> </head> <body><div id="logDisplay"></div></body> </html>
The embedded worker is now nested into a new custom document.worker property.
It is also worth noting that you can also convert a function into a Blob, then generate an object URL from that blob. For example:
function fn2workerURL(fn) { var blob = new Blob(['('+fn.toString()+')()'], {type: 'application/javascript'}) return URL.createObjectURL(blob) }
This section provides further examples of how to use web workers.
Performing computations in the background
Workers are mainly useful for allowing your code to perform processor-intensive calculations without blocking the user interface thread. In this example, a worker is used to calculate Fibonacci numbers.
The JavaScript code
The following JavaScript code is stored in the "fibonacci.js" file referenced by the HTML in the next section.
var results = []; function resultReceiver(event) { results.push(parseInt(event.data)); if (results.length == 2) { postMessage(results[0] + results[1]); } } function errorReceiver(event) { throw event.data; } onmessage = function(event) { var n = parseInt(event.data); if (n == 0 || n == 1) { postMessage(n); return; } for (var i = 1; i <= 2; i++) { var worker = new Worker("fibonacci.js"); worker.onmessage = resultReceiver; worker.onerror = errorReceiver; worker.postMessage(n - i); } };
The worker sets the property onmessage to a function which will receive messages sent when the worker object's postMessage() is called (note that this differs from defining a global variable of that name, or defining a function with that name. var onmessage and function onmessage will define global properties with those names, but they will not register the function to receive messages sent by the web page that created the worker). This starts the recursion, spawning new copies of itself to handle each iteration of the calculation.
The web page creates a div element with the ID result , which gets used to display the result, then spawns the worker. After spawning the worker, the onmessage handler is configured to display the results by setting the contents of the div element, and the onerror handler is set to dump the error message.
Finally, a message is sent to the worker to start it.
As multi-core computers become increasingly common, it's often useful to divide computationally complex tasks among multiple workers, which may then perform those tasks on multiple-processor cores.
In addition to dedicated and shared web workers, there are other types of worker available:
ServiceWorkers essentially act as proxy servers that sit between web applications, and the browser and network (when available). They are intended to (amongst other things) enable the creation of effective offline experiences, intercepting network requests and taking appropriate action based on whether the network is available and updated assets reside on the server. They will also allow access to push notifications and background sync APIs.
Chrome Workers are a Firefox-only type of worker that you can use if you are developing add-ons and want to use workers in extensions and have access to js-ctypes in your worker. See ChromeWorker for more details.
Audio Workers provide the ability for direct scripted audio processing to be done in a web worker context.
The main thing you can't do in a Worker is directly affect the parent page. This includes manipulating the DOM and using that page's objects. You have to do it indirectly, by sending a message back to the main script via DedicatedWorkerGlobalScope.postMessage, then actioning the changes from there.
There are a number of bottlenecks preventing interesting applications from being ported (say, from server-heavy implementations) to client-side JavaScript. Some of these include browser compatibility, static typing, accessibility, and performance. Fortunately, the latter is quickly becoming a thing of the past as browser vendors rapidly improve the speed of their JavaScript engines.
One thing that's remained a hindrance for JavaScript is actually the language itself. JavaScript is a single-threaded environment, meaning multiple scripts cannot run at the same time. As an example, imagine a site that needs to handle UI events, query and process large amounts of API data, and manipulate the DOM. Pretty common, right? Unfortunately all of that can't be simultaneous due to limitations in browsers' JavaScript runtime. Script execution happens within a single thread.
Developers mimic 'concurrency' by using techniques like setTimeout(), setInterval(), XMLHttpRequest, and event handlers. Yes, all of these features run asynchronously, but non-blocking doesn't necessarily mean concurrency. Asynchronous events are processed after the current executing script has yielded. The good news is that HTML5 gives us something better than these hacks!
Introducing Web Workers: Bring Threading to JavaScript
The Web Workers specification defines an API for spawning background scripts in your web application. Web Workers allow you to do things like fire up long-running scripts to handle computationally intensive tasks, but without blocking the UI or other scripts to handle user interactions. They're going to help put and end to that nasty 'unresponsive script' dialog that we've all come to love:
Common unresponsive script dialog.
Workers utilize thread-like message passing to achieve parallelism. They're perfect for keeping your UI refresh, performant, and responsive for users.
Types of Web Workers
It's worth noting that the specification discusses two kinds of Web Workers, Dedicated Workers and Shared Workers. This article will only cover dedicated workers and I'll refer to them as 'web workers' or 'workers' throughout.
Getting Started
Web Workers run in an isolated thread. As a result, the code that they execute needs to be contained in a separate file. But before we do that, the first thing to do is create a new Worker object in your main page. The constructor takes the name of the worker script:
var worker = new Worker('task.js');
If the specified file exists, the browser will spawn a new worker thread, which is downloaded asynchronously. The worker will not begin until the file has completely downloaded and executed. If the path to your worker returns an 404, the worker will fail silently.
After creating the worker, start it by calling the postMessage() method:
worker.postMessage(); // Start the worker.
Communicating with a Worker via Message Passing
Communication between a work and its parent page is done using an event model and the postMessage() method. Depending on your browser/version, postMessage() can accept either a string or JSON object as its single argument. The latest versions of the modern browsers support passing a JSON object.
Below is a example of using a string to pass 'Hello World' to a worker in doWork.js. The worker simply returns the message that is passed to it.
Main script:
var worker = new Worker('doWork.js'); worker.addEventListener('message', function(e) { console.log('Worker said: ', e.data); }, false); worker.postMessage('Hello World'); // Send data to our worker.
When postMessage() is called from the main page, our worker handles that message by defining an onmessage handler for the message event. The message payload (in this case 'Hello World') is accessible in Event.data. Although this particular example isn't very exciting, it demonstrates that postMessage() is also your means for passing data back to the main thread. Convenient!
Messages passed between the main page and workers are copied, not shared. For example, in the next example the 'msg' property of the JSON message is accessible in both locations. It appears that the object is being passed directly to the worker even though it's running in a separate, dedicated space. In actuality, what is happening is that the object is being serialized as it's handed to the worker, and subsequently, de-serialized on the other end. The page and worker do not share the same instance, so the end result is that a duplicate is created on each pass. Most browsers implement this feature by automatically JSON encoding/decoding the value on either end.
The following is a more complex example that passes messages using JSON objects.
Main script:
<button onclick="sayHI()">Say HI</button> <button onclick="unknownCmd()">Send unknown command</button> <button onclick="stop()">Stop worker</button> <output id="result"></output> <script> function sayHI() { worker.postMessage({'cmd': 'start', 'msg': 'Hi'}); } function stop() { // worker.terminate() from this script would also stop the worker. worker.postMessage({'cmd': 'stop', 'msg': 'Bye'}); } function unknownCmd() { worker.postMessage({'cmd': 'foobard', 'msg': '???'}); } var worker = new Worker('doWork2.js'); worker.addEventListener('message', function(e) { document.getElementById('result').textContent = e.data; }, false); </script>
doWork2.js:
self.addEventListener('message', function(e) { var data = e.data; switch (data.cmd) { case 'start': self.postMessage('WORKER STARTED: ' + data.msg); break; case 'stop': self.postMessage('WORKER STOPPED: ' + data.msg + '. (buttons will no longer work)'); self.close(); // Terminates the worker. break; default: self.postMessage('Unknown command: ' + data.msg); }; }, false);
Note: There are two ways to stop a worker: by calling worker.terminate() from the main page or by calling self.close() inside of the worker itself.
Example: Run this worker!
Transferrable objects
Most browsers implement the structured cloning algorithm, which allows you to pass more complex types in/out of Workers such as File, Blob, ArrayBuffer, and JSON objects. However, when passing these types of data usingpostMessage(), a copy is still made. Therefore, if you're passing a large 50MB file (for example), there's a noticeable overhead in getting that file between the worker and the main thread.
Structured cloning is great, but a copy can take hundreds of milliseconds. To combat the perf hit, you can use Transferable Objects.
With Transferable Objects, data is transferred from one context to another. It is zero-copy, which vastly improves the performance of sending data to a Worker. Think of it as pass-by-reference if you're from the C/C++ world. However, unlike pass-by-reference, the 'version' from the calling context is no longer available once transferred to the new context. For example, when transferring an ArrayBuffer from your main app to Worker, the original ArrayBuffer is cleared and no longer usable. Its contents are (quiet literally) transferred to the Worker context.
To use transferrable objects, use a slightly different signature of postMessage():
The worker case, the first argument is the data and the second is the list of items that should be transferred. The first argument doesn't have to be an ArrayBufferby the way. For example, it can be a JSON object:
You can load external script files or libraries into a worker with the importScripts() function. The method takes zero or more strings representing the filenames for the resources to import.
This example loads script1.js and script2.js into the worker:
Which can also be written as a single import statement:
importScripts('script1.js', 'script2.js');
Subworkers
Workers have the ability to spawn child workers. This is great for further breaking up large tasks at runtime. However, subworkers come with a few caveats:
Subworkers must be hosted within the same origin as the parent page.
URIs within subworkers are resolved relative to their parent worker's location (as opposed to the main page).
Keep in mind most browsers spawn separate processes for each worker. Before you go spawning a worker farm, be cautious about hogging too many of the user's system resources. One reason for this is that messages passed between main pages and workers are copied, not shared. See Communicating with a Worker via Message Passing.
For an sample of how to spawn a subworker, see the example in the specification.
Inline Workers
What if you want to create your worker script on the fly, or create a self-contained page without having to create separate worker files? With Blob(), you can "inline" your worker in the same HTML file as your main logic by creating a URL handle to the worker code as a string:
var blob = new Blob([ "onmessage = function(e) { postMessage('msg from worker'); }"]); // Obtain a blob URL reference to our worker 'file'. var blobURL = window.URL.createObjectURL(blob); var worker = new Worker(blobURL); worker.onmessage = function(e) { // e.data == 'msg from worker' }; worker.postMessage(); // Start the worker.
Blob URLs
The magic comes with the call to window.URL.createObjectURL(). This method creates a simple URL string which can be used to reference data stored in a DOM File or Blob object. For example:
Blob URLs are unique and last for the lifetime of your application (e.g. until the document is unloaded). If you're creating many Blob URLs, it's a good idea to release references that are no longer needed. You can explicitly release a Blob URLs by passing it to window.URL.revokeObjectURL():
window.URL.revokeObjectURL(blobURL);
In Chrome, there's a nice page to view all of the created blob URLs: chrome://blob-internals/.
Full Example
Taking this one step further, we can get clever with how the worker's JS code is inlined in our page. This technique uses a <script> tag to define the worker:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> </head> <body> <div id="log"></div> <script id="worker1" type="javascript/worker"> // This script won't be parsed by JS engines // because its type is javascript/worker. self.onmessage = function(e) { self.postMessage('msg from worker'); }; // Rest of your worker code goes here. </script> <script> function log(msg) { // Use a fragment: browser will only render/reflow once. var fragment = document.createDocumentFragment(); fragment.appendChild(document.createTextNode(msg)); fragment.appendChild(document.createElement('br')); document.querySelector("#log").appendChild(fragment); } var blob = new Blob([document.querySelector('#worker1').textContent]); var worker = new Worker(window.URL.createObjectURL(blob)); worker.onmessage = function(e) { log("Received: " + e.data); } worker.postMessage(); // Start the worker. </script> </body> </html>
In my opinion, this new approach is a bit cleaner and more legible. It defines a script tag with
id="worker1"
and
type='javascript/worker'
(so the browser doesn't parse the JS). That code is extracted as a string using document.querySelector('#worker1').textContent and passed to Blob() to create the file.
Loading External Scripts
When using these techniques to inline your worker code, importScripts() will only work if you supply an absolute URI. If you attempt to pass a relative URI, the browser will complain with a security error. The reason being: the worker (now created from a blob URL) will be resolved with a blob: prefix, while your app will be running from a different (presumably http://) scheme. Hence, the failure will be due to cross origin restrictions.
One way to utilize importScripts() in an inline worker is to "inject" the current url of your main script is running from by passing it to the inline worker and constructing the absolute URL manually. This will insure the external script is imported from the same origin. Assuming your main app is running from http://example.com/index.html:
... <script id="worker2" type="javascript/worker"> self.onmessage = function(e) { var data = e.data; if (data.url) { var url = data.url.href; var index = url.indexOf('index.html'); if (index != -1) { url = url.substring(0, index); } importScripts(url + 'engine.js'); } ... }; </script> <script> var worker = new Worker(window.URL.createObjectURL(bb.getBlob())); worker.postMessage({url: document.location}); </script>
Handling Errors
As with any JavaScript logic, you'll want to handle any errors that are thrown in your web workers. If an error occurs while a worker is executing, the an ErrorEvent is fired. The interface contains three useful properties for figuring out what went wrong: filename - the name of the worker script that caused the error, lineno - the line number where the error occurred, and message - a meaningful description of the error. Here is an example of setting up an onerror event handler to print the properties of the error:
<output id="error" style="color: red;"></output> <output id="result"></output> <script> function onError(e) { document.getElementById('error').textContent = [ 'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message ].join(''); } function onMsg(e) { document.getElementById('result').textContent = e.data; } var worker = new Worker('workerWithError.js'); worker.addEventListener('message', onMsg, false); worker.addEventListener('error', onError, false); worker.postMessage(); // Start worker without a message. </script>
Example: workerWithError.js tries to perform 1/x, where x is undefined.
Due to Google Chrome's security restrictions, workers will not run locally (e.g. from file://) in the latest versions of the browser. Instead, they fail silently! To run your app from the file:// scheme, run Chrome with the --allow-file-access-from-files flag set. NOTE: It is not recommended to run your primary browser with this flag set. It should only be used for testing purposes and not regular browsing.
Other browsers do not impose the same restriction.
Same Origin Considerations
Worker scripts must be external files with the same scheme as their calling page. Thus, you cannot load a script from a data: URL or javascript: URL, and an https: page cannot start worker scripts that begin with http: URLs.
Use Cases
So what kind app would utilize web workers? Unfortunately, web workers are still relatively new and the majority of samples/tutorials out there involve computing prime numbers. Although that isn't very interesting, it's useful for understanding the concepts of web workers. Here are a few more ideas to get your brain churning:
Prefetching and/or caching data for later use
Code syntax highlighting or other real-time text formatting
Spell checker
Analyzing video or audio data
Background I/O or polling of webservices
Processing large arrays or humungous JSON responses
WebWorker란, HTML5 API중 하나로 JavaScript를 병렬 처리하기 위해서 표준으로 준비된 규격입니다. 일반적으로 JavaScript는 CPU의 한개 쓰레드로 연산을 하면, 시간이 걸리는 연산이라고 해도 해당 연산이 끝날 때까지 다음 명령을 받아들이지 않습니다. 반대로 WebWorker를 이용하면, 미리 처리를 정의해둔 Worker라는 임의의 타이밍을 호출하여 무거운 연산의 멀티쓰레드 처리를 가능하게 합니다. 즉, 최근 멀티코어 기기에서 JavaScript를 이용한 연산을 효율적으로 할 수 있게 되는 것입니다.
WebWorker 사용법
멀티쓰레드 처리라고 말하면 어럽게 느껴질 수 있지만, WebWorker를 사용하는 방법은 상당히 간단합니다. 라이브러리를 별도로 읽을 필요도 없이 다음 4가지 절차 순서대로 진행하면 됩니다.
1. Worker클래스 객체 선언
2. Worker 변수 보내기
3. Worker 실행하는 처리 정의
4. Worker로부터 처리결과 받음
1. Worker 클래스 객체 선언
var worker = new Worker("Worker로 실행하는 처리 기술하는 파일명");
이렇게만 선언하면 되는데 Worker로 실행하는 처리를 기술하는 파일명을 "worker_test.js"라고 합니다. 물론 객체명은 "worker"는 임의대로 선언합니다.
2. Worker 변수 보내기
worker.postMessage(변수명); //Worker에 변수 넘김
"worker"는 1번에서 선언한 Worker클래스 객체명으로 "postMessage"는 Worker클래스 객체 메소드입니다. "postMessage"에 인수를 설정하는 것으로 외부에서 실행하는 처리에 파라미터를 보낼 수 있습니다. 인수에는 보통 변수뿐만 아니라, 배열이나 객체를 줄 수 있습니다. 객체를 인수로서 사용하는 예제는 다음과 같습니다.
var AA = {n:1, l:2, m:3}; //Worker에 넘기는 객체 선언 worker.postMessage(AA); //Worker에 객체 넘김
3. Worker실행하는 처리 정의
1번에서 지정한 파일에 2로 넘긴 파라미터를 이용하여 Worker로 실행하는 처리를 만듭니다.
worker_test.js
onmessage = function(event) { var AA = event.data; // 여기에 처리를 기술함 var results = AA; postMessage(results); }
"event.data"에 2번 "postMessage" 인수로 설정한 변수가 있습니다. 또한 "postMessage"함수를 이용하여 처리결과를 호출하는 객체에 리턴합니다. 위 예는 동작 확인을 목적으로 하고 있으므로 받은 변수를 아무것도 처리를 하지 않고 그대로 리턴하고 있습니다.
4. Worker로부터 처리결과 받음
3번 "postMessage"함수 인수로 지정한 변수를 원래 프로그램으로 받습니다. 변수가 리턴되어 오면, Worker클래스 객체 메소드 "onmessage"가 호출됩니다.
worker.onmessage = function(event) { var BB = event.data; }
"event.data"에는 3번 "postMessage" 인수로 설정한 변수가 포함되어 있습니다. 받은 처리후 데이터를 나머지를 처리하게 됩니다.
예제 프로그램 소스코드
메인소스코드
var worker = new Worker("worker_test.js"); worker.onmessage = function(event) { //Worker로부터 받음 var BB = event.data; document.getElementById("output").innerHTML = "n = " + BB.n + "<br>l = " + BB.l + "<br>m = " + BB.m; } function pushButton () { //버튼을 클릭했을 때 처리 var AA = {n:1, l:2, m:3}; worker.postMessage(AA); // Worker에 값을 넘김 };
worker_test.js
onmessage = function(event) { var AA = event.data; // event.data // 여기에 처리를 작성 var results = AA; postMessage(results); }
<script type="text/javascript" src="http://code.jquery.com/jquery-1.11.1.min.js"></script> <script type="text/javascript"> //worker객체 생성 //백그라운드 실행 코드가있는 js파일명 등록 var worker = new Worker('worker1.js'); $(function(){ $('#btnOk').click(function(){ //postMessage: 백그라운드 작업을 수행하는 객체에 데이터 전달 worker.postMessage($('#guguNum').val()); }); //onmessage: 백그라운드에서 데이터 작업을 완료한 후 postMessage메서드에 데이터를 전달하면 이벤트 가 발생 worker.onmessage = function(event){ $('#result').html(event.data);//백그라운드 실행 결과값을 반환 }; worker.onerror = function(event){ $('#result').html('에러 발생!'); } $('#btnStop').click(function(){ if(worker) worker.terminate(); }); });
Web Workers are, undoubtedly, the coolest new feature to arrive in the latest version of web browsers. Web Workers allow you to run JavaScript in parallel on a web page, without blocking the user interface.
Normally in order to achieve any sort of computation using JavaScript you would need to break your jobs up into tiny chunks and split their execution apart using timers. This is both slow and unimpressive (since you can’t actually run anything in parallel – more information on this in How JavaScript Timers Work).
With our current situation in mind, let’s dig in to Web Workers.
Web Workers
The Web Worker recommendation is partially based off of the prior work done by the Gears team on their WorkerPool Module. The idea has since grown and been tweaked to become a full recommendation.
A ‘worker’ is a script that will be loaded and executed in the background. Web Workers provide a way to do this seamlessly, for example:
newWorker("worker.js");
The above will load the script, located at ‘worker.js’, and execute it in the background.
There are some HUGE stipulations, though:
Workers don’t have access to the DOM. No document, getElementById, etc. (The notable exceptions are setTimeout, setInterval, and XMLHttpRequest.)
Workers don’t have direct access to the ‘parent’ page.
With these points in mind the big question should be: How do you actually use a worker and what is it useful for?
You use a worker by communicating with it using messages. All browsers support passing in a string message (Firefox 3.5 also supports passing in JSON-compatible objects). This message will be communicated to the worker (the worker can also communicate messages back to the parent page). This is the extent to which communication can occur.
The message passing is done using the postMessage API, working like this:
varworker = newWorker("worker.js");
// Watch for messages from the worker
worker.onmessage = function(e){
// The message from the client:
e.data
};
worker.postMessage("start");
The Client:
onmessage = function(e){
if( e.data === "start") {
// Do some computation
done()
}
};
functiondone(){
// Send back the results to the parent page
postMessage("done");
}
This particular message-passing limitation is in place for a number of reasons: It keeps the child worker running securely (since it can’t, blatantly, affect a parent script) and it keeps the parent page thread-safe (having the DOM be thread safe would be a logistical nightmare for browser developers).
Right now Web Workers are implemented by Firefox 3.5 and Safari 4. They’ve also landed in the latest Chromium nightlies. Most people would balk when hearing this (only two released browsers!) but this shouldn’t be a concern. Workers allow you to take a normal piece of computation and highly parallelize it. In this way you can easily have two versions of a script (one that runs in older browsers and one that runs in a worker, if it’s available). Newer browsers will just run that much faster.
Some interesting demos have already been created that utilize this new API.
RayTracing
This demo makes use of Canvas to draw out a rendered scene. You’ll note that when you turn on the workers the scene is drawn in pieces. This is working by telling a worker to compute a slice of pixels. The worker responds with an array of colors to draw on the Canvas and the parent page changes the canvas. (Note that the worker itself doesn’t manipulate the canvas.)
Movement Tracking
(Requires Firefox 3.5. About the demo.) This one uses a number of technologies: The video element, the canvas element, and drawing video frames to a canvas. All of the motion detection it taking place in the background worker (so that the video rendering isn’t blocked).
Simulated Annealing
This demo attempts to draw outlines around a series of randomly-placed points using simulated annealing (More information). It also includes an animated PNG (works in Firefox 3.5) that continues to spin even while all the processing is occurring in the background.
Computing with JavaScript Web Workers
The other day Engine Yard started an interesting contest (which is probably over, by the time that you’re reading this).
The premise is that they would give you a phrase, which you would take the SHA1 of, and try to find another SHA1-ed string that has the smallest possible hamming distance from the original.
The phrase was posted the other day and developers have been furiously working to find a string that yields a low value.
The current leader is using a series of dedicated GPUs crunching out results at a pace of a couple hundred million per second. Considering the rate at which they’re progressing any other implementation will have a hard time catching up.
Of greater interest to me were two pure-JavaScript (1, 2) entrants into the competition – they both run completely in the browser and utilize the user’s JavaScript engine to find results. While neither of them have a prayer of overcoming the GPU-powered monsters dominating the pack, they do serve as an interesting realm for exploration.
Reading through the source to both implementations they both utilize nearly-identical tactics for computing results: They execute a batch of results broken up by a timer. I’ve played around with them in different browsers and have been able to get around 1000-1500 matches/second. Unfortunately they both peg the CPU pretty hard and even with the timer splitting they manage to bog down the user interface.
This sounds like a perfect opportunity to use Web Workers!
I took the Ray C Morgan implementation, stripped out all the UI components and timers, and pushed it in to worker (through which 4 of them are run in parallel). (I submit results back to the original implementation, just in case a good result is found.)
// Build a worker var worker = new Worker(“worker.js”);
// Listen for incoming messages worker.onmessage = function(e){ var parts = e.data.split(” “);
// We’re getting the rate at which computations are done if ( parts[0] === “rate” ) { rates[i] = parseInt(parts[1]);
// Total the rates from all the workers var total = 0; for ( var j = 0; j < rates.length; j++ ) { total += rates[j]; } num.innerHTML = total; // We've found a new best score, send it to the server } else if ( parts[0] === "found" ) { var img = document.createElement("img"); img.src = "http://www.raycmorgan.com/new-best?phrase=" + escape(parts.slice(1).join(" ")); document.body.appendChild( img ); // A new personal best score was found } else if ( parts[0] === "mybest" ) { var tmp = parseInt(parts[1]); if ( tmp < mybest ) { mybest = tmp; best.innerHTML = mybest; } } }; // Start the worker worker.postMessage( data.sha + " " + data.words.join(",") + " " + data.best );[/js] To start, we're constructing the worker and listening for any incoming messages. There are three types of messages that can come from the worker: "rate" (a 'ping' from the worker notifying the parent how quickly it's running), "found" (sent back when a new high scoring phrase has been found by the client), and "mybest" (sent when the worker gets a new personal-best high score). Additionally we can see the initialization data sent to the client in worker.postMessage. Unfortunately we have to pass the data in using a string in order to have it work in all browsers (only Firefox 3.5 supports the ability to pass in a raw JavaScript object).
Looking at the contents of the worker we can see some more, interesting, logic.
// … snip …
// New Personal Best Found if (distance < myBest) { myBest = distance; postMessage("mybest " + myBest); } // New All-time Best Found if (distance < best) { best = distance; postMessage("found " + phrase); } // ... snip ... // Report Rate Back to Parent function stats() { var nowDiff = (new Date()).getTime() - startTime; var perSec = Math.floor(processed/nowDiff*1000); postMessage( "rate " + perSec ); } // ... snip ... // Get the incoming information from the parent onmessage = function(e){ var parts = e.data.split(" "); data = { sha: parts[0], words: parts[1].split(","), best: parts[2] }; start(); };[/js] The two 'distance' checks take place deep in the computation logic. After a new match has been found it is compared against the existing high scores. If this a sufficiently good-enough the result is sent back to the parent page using postMessage.
The ‘stats’ function is called periodically, which then reports back the current rate of processing to the parent page.
The ‘onmessage’ callback listens for the initialization data to come from the parent page – and once it’s been received begins processing.
—
In all I found this project to be a lot of fun – a relatively minor amount of code yielded 2-3x faster computation power. If you’re doing any computation with JavaScript you should definitely opt to use Web Workers if they’re available – the result is both faster and a better experience for the end user.
SharedWorker 인터페이스는 윈도우 창이나 iframe, 워커등의 다른 브라우징 컨텍스트에서도 접근이 가능한 특정 종류의 워커를 의미합니다. 기존의 다른 종류의 워커들과 다른 전역 스코프를 갖는 인터페이스를 구현합니다. SharedWorkerGlobalScope.
알아둘 점 : SharedWorker 가 몇개의 다른 브라우징 컨텍스트에서 접근이 가능하면, 그 브라우징 컨텍스트들은 모두 정확히 같은 오리진을 공유해야 합니다. (같은 프로토콜, 호스트, 포트 등)
알아둘 점 : 파이어폭스에서, shared workers 는 개인, 비개인 윈도우 간에 공유될 수 없습니다. (확인 bug 1177621.)
Basic shared worker example (run shared worker) 를 보시면 2개의 HTML 페이지가 있습니다. 각각 간단한 계산을 위해 자바스크립트를 사용합니다. 각기 다른 스크립트가 계산을 위해 같은 워커 파일을 사용합니다 — 두 개 페이지가 모두 다른 윈도우창에서 실행되더라도 같은 워커에 접근할 수 있습니다.
아래 코드 스니펫은 SharedWorker() 생성자를 이용해 SharedWorker 객체를 생성합니다. 두 스크립트 모두 아래를 포함합니다.
var myWorker = new SharedWorker("worker.js");
두 스크립트는 SharedWorker.port 속성으로 생성한 MessagePort 객체를 통해 워커에 접근할 수 있습니다. addEventListener 를 이용하여 onmessage 가 추가되면, port는 start() 메서드를 이용하여 수동으로 시작할 수 있습니다.
myWorker.port.start();
포트가 시작되면, 양 스크립트는 워커에 메시지를 보내고 port.postMessage()와 port.onmessage 를 각각 이용하여 메시지를 처리합니다.
first.onchange = function() { myWorker.port.postMessage([first.value,second.value]); console.log('Message posted to worker'); } second.onchange = function() { myWorker.port.postMessage([first.value,second.value]); console.log('Message posted to worker'); } myWorker.port.onmessage = function(e) { result1.textContent = e.data; console.log('Message received from worker'); }
onconnect = function(e) { var port = e.ports[0]; port.addEventListener('message', function(e) { var workerResult = 'Result: ' + (e.data[0] * e.data[1]); port.postMessage(workerResult); }); port.start(); // Required when using addEventListener. Otherwise called implicitly by onmessage setter. }
Specifications
=======================
=======================
=======================
출처: https://html.spec.whatwg.org/#toc-workers
10 Web workers
10.1 Introduction
10.1.1 Scope
This section is non-normative.
This specification defines an API for running scripts in the background independently of any user interface scripts.
This allows for long-running scripts that are not interrupted by scripts that respond to clicks or other user interactions, and allows long tasks to be executed without yielding to keep the page responsive.
Workers (as these background scripts are called herein) are relatively heavy-weight, and are not intended to be used in large numbers. For example, it would be inappropriate to launch one worker for each pixel of a four megapixel image. The examples below show some appropriate uses of workers.
Generally, workers are expected to be long-lived, have a high start-up performance cost, and a high per-instance memory cost.
10.1.2 Examples
This section is non-normative.
There are a variety of uses that workers can be put to. The following subsections show various examples of this use.
10.1.2.1 A background number-crunching worker
This section is non-normative.
The simplest use of workers is for performing a computationally expensive task without interrupting the user interface.
In this example, the main document spawns a worker to (naïvely) compute prime numbers, and progressively displays the most recently found prime number.
The main page is as follows:
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>Worker example: One-core computation</title> </head> <body> <p>The highest prime number discovered so far is: <output id="result"></output></p> <script> var worker = new Worker('worker.js'); worker.onmessage = function (event) { document.getElementById('result').textContent = event.data; }; </script> </body> </html>
The Worker() constructor call creates a worker and returns a Worker object representing that worker, which is used to communicate with the worker. That object's onmessageevent handler allows the code to receive messages from the worker.
The worker itself is as follows:
var n = 1; search: while (true) { n += 1; for (var i = 2; i <= Math.sqrt(n); i += 1) if (n % i == 0) continue search; // found a prime! postMessage(n); }
The bulk of this code is simply an unoptimized search for a prime number. The postMessage() method is used to send a message back to the page when a prime is found.
All of our examples so far show workers that run classic scripts. Workers can instead be instantiated using module scripts, which have the usual benefits: the ability to use the JavaScript import statement to import other modules; strict mode by default; and top-level declarations not polluting the worker's global scope.
Note that such module-based workers follow different restrictions regarding cross-origin content, compared to classic workers. Unlike classic workers, module workers can be instantiated using a cross-origin script, as long as that script is exposed using the CORS protocol. Additionally, the importScripts() method will automatically fail inside module workers; the JavaScript import statement is generally a better choice.
In this example, the main document uses a worker to do off-main-thread image manipulation. It imports the filters used from another module.
import * as filters from "./filters.js"; self.onmessage = e => { const { imageData, filter } = e.data; filters[filter](imageData); self.postMessage(imageData, [imageData.data.buffer]); };
Which imports the file filters.js:
export function none() {} export function grayscale({ data: d }) { for (let i = 0; i < d.length; i += 4) { const [r, g, b] = [d[i], d[i + 1], d[i + 2]]; // CIE luminance for the RGB // The human eye is bad at seeing red and blue, so we de-emphasize them. d[i] = d[i + 1] = d[i + 2] = 0.2126 * r + 0.7152 * g + 0.0722 * b; } }; export function brighten({ data: d }) { for (let i = 0; i < d.length; ++i) { d[i] *= 1.2; } };
This section introduces shared workers using a Hello World example. Shared workers use slightly different APIs, since each worker can have multiple connections.
This first example shows how you connect to a worker and how a worker can send a message back to the page when it connects to it. Received messages are displayed in a log.
Here is the HTML page:
<!DOCTYPE HTML> <meta charset="utf-8"> <title>Shared workers: demo 1</title> <pre id="log">Log:</pre> <script> var worker = new SharedWorker('test.js'); var log = document.getElementById('log'); worker.port.onmessage = function(e) { // note: not worker.onmessage! log.textContent += '\n' + e.data; } </script>
Here is the JavaScript worker:
onconnect = function(e) { var port = e.ports[0]; port.postMessage('Hello World!'); }
This second example extends the first one by changing two things: first, messages are received using addEventListener() instead of an event handler IDL attribute, and second, a message is sent to the worker, causing the worker to send another message in return. Received messages are again displayed in a log.
Here is the HTML page:
<!DOCTYPE HTML> <meta charset="utf-8"> <title>Shared workers: demo 2</title> <pre id="log">Log:</pre> <script> var worker = new SharedWorker('test.js'); var log = document.getElementById('log'); worker.port.addEventListener('message', function(e) { log.textContent += '\n' + e.data; }, false); worker.port.start(); // note: need this when using addEventListener worker.port.postMessage('ping'); </script>
Here is the JavaScript worker:
onconnect = function(e) { var port = e.ports[0]; port.postMessage('Hello World!'); port.onmessage = function(e) { port.postMessage('pong'); // not e.ports[0].postMessage! // e.target.postMessage('pong'); would work also } }
Finally, the example is extended to show how two pages can connect to the same worker; in this case, the second page is merely in an iframe on the first page, but the same principle would apply to an entirely separate page in a separate top-level browsing context.
In this example, multiple windows (viewers) can be opened that are all viewing the same map. All the windows share the same map information, with a single worker coordinating all the viewers. Each viewer can move around independently, but if they set any data on the map, all the viewers are updated.
The main page isn't interesting, it merely provides a way to open the viewers:
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>Workers example: Multiviewer</title> <script> function openViewer() { window.open('viewer.html'); } </script> </head> <body> <p><button type=button onclick="openViewer()">Open a new viewer</button></p> <p>Each viewer opens in a new window. You can have as many viewers as you like, they all view the same data.</p> </body> </html>
The viewer is more involved:
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>Workers example: Multiviewer viewer</title> <script> var worker = new SharedWorker('worker.js', 'core'); // CONFIGURATION function configure(event) { if (event.data.substr(0, 4) != 'cfg ') return; var name = event.data.substr(4).split(' ', 1)[0]; // update display to mention our name is name document.getElementsByTagName('h1')[0].textContent += ' ' + name; // no longer need this listener worker.port.removeEventListener('message', configure, false); } worker.port.addEventListener('message', configure, false); // MAP function paintMap(event) { if (event.data.substr(0, 4) != 'map ') return; var data = event.data.substr(4).split(','); // display tiles data[0] .. data[8] var canvas = document.getElementById('map'); var context = canvas.getContext('2d'); for (var y = 0; y < 3; y += 1) { for (var x = 0; x < 3; x += 1) { var tile = data[y * 3 + x]; if (tile == '0') context.fillStyle = 'green'; else context.fillStyle = 'maroon'; context.fillRect(x * 50, y * 50, 50, 50); } } } worker.port.addEventListener('message', paintMap, false); // PUBLIC CHAT function updatePublicChat(event) { if (event.data.substr(0, 4) != 'txt ') return; var name = event.data.substr(4).split(' ', 1)[0]; var message = event.data.substr(4 + name.length + 1); // display "<name> message" in public chat var public = document.getElementById('public'); var p = document.createElement('p'); var n = document.createElement('button'); n.textContent = '<' + name + '> '; n.onclick = function () { worker.port.postMessage('msg ' + name); }; p.appendChild(n); var m = document.createElement('span'); m.textContent = message; p.appendChild(m); public.appendChild(p); } worker.port.addEventListener('message', updatePublicChat, false); // PRIVATE CHAT function startPrivateChat(event) { if (event.data.substr(0, 4) != 'msg ') return; var name = event.data.substr(4).split(' ', 1)[0]; var port = event.ports[0]; // display a private chat UI var ul = document.getElementById('private'); var li = document.createElement('li'); var h3 = document.createElement('h3'); h3.textContent = 'Private chat with ' + name; li.appendChild(h3); var div = document.createElement('div'); var addMessage = function(name, message) { var p = document.createElement('p'); var n = document.createElement('strong'); n.textContent = '<' + name + '> '; p.appendChild(n); var t = document.createElement('span'); t.textContent = message; p.appendChild(t); div.appendChild(p); }; port.onmessage = function (event) { addMessage(name, event.data); }; li.appendChild(div); var form = document.createElement('form'); var p = document.createElement('p'); var input = document.createElement('input'); input.size = 50; p.appendChild(input); p.appendChild(document.createTextNode(' ')); var button = document.createElement('button'); button.textContent = 'Post'; p.appendChild(button); form.onsubmit = function () { port.postMessage(input.value); addMessage('me', input.value); input.value = ''; return false; }; form.appendChild(p); li.appendChild(form); ul.appendChild(li); } worker.port.addEventListener('message', startPrivateChat, false); worker.port.start(); </script> </head> <body> <h1>Viewer</h1> <h2>Map</h2> <p><canvas id="map" height=150 width=150></canvas></p> <p> <button type=button onclick="worker.port.postMessage('mov left')">Left</button> <button type=button onclick="worker.port.postMessage('mov up')">Up</button> <button type=button onclick="worker.port.postMessage('mov down')">Down</button> <button type=button onclick="worker.port.postMessage('mov right')">Right</button> <button type=button onclick="worker.port.postMessage('set 0')">Set 0</button> <button type=button onclick="worker.port.postMessage('set 1')">Set 1</button> </p> <h2>Public Chat</h2> <div id="public"></div> <form onsubmit="worker.port.postMessage('txt ' + message.value); message.value = ''; return false;"> <p> <input type="text" name="message" size="50"> <button>Post</button> </p> </form> <h2>Private Chat</h2> <ul id="private"></ul> </body> </html>
There are several key things worth noting about the way the viewer is written.
Multiple listeners. Instead of a single message processing function, the code here attaches multiple event listeners, each one performing a quick check to see if it is relevant for the message. In this example it doesn't make much difference, but if multiple authors wanted to collaborate using a single port to communicate with a worker, it would allow for independent code instead of changes having to all be made to a single event handling function.
Registering event listeners in this way also allows you to unregister specific listeners when you are done with them, as is done with the configure() method in this example.
Finally, the worker:
var nextName = 0; function getNextName() { // this could use more friendly names // but for now just return a number return nextName++; } var map = [ [0, 0, 0, 0, 0, 0, 0], [1, 1, 0, 1, 0, 1, 1], [0, 1, 0, 1, 0, 0, 0], [0, 1, 0, 1, 0, 1, 1], [0, 0, 0, 1, 0, 0, 0], [1, 0, 0, 1, 1, 1, 1], [1, 1, 0, 1, 1, 0, 1], ]; function wrapX(x) { if (x < 0) return wrapX(x + map[0].length); if (x >= map[0].length) return wrapX(x - map[0].length); return x; } function wrapY(y) { if (y < 0) return wrapY(y + map.length); if (y >= map[0].length) return wrapY(y - map.length); return y; } function wrap(val, min, max) { if (val < min) return val + (max-min)+1; if (val > max) return val - (max-min)-1; return val; } function sendMapData(viewer) { var data = ''; for (var y = viewer.y-1; y <= viewer.y+1; y += 1) { for (var x = viewer.x-1; x <= viewer.x+1; x += 1) { if (data != '') data += ','; data += map[wrap(y, 0, map[0].length-1)][wrap(x, 0, map.length-1)]; } } viewer.port.postMessage('map ' + data); } var viewers = {}; onconnect = function (event) { var name = getNextName(); event.ports[0]._data = { port: event.ports[0], name: name, x: 0, y: 0, }; viewers[name] = event.ports[0]._data; event.ports[0].postMessage('cfg ' + name); event.ports[0].onmessage = getMessage; sendMapData(event.ports[0]._data); }; function getMessage(event) { switch (event.data.substr(0, 4)) { case 'mov ': var direction = event.data.substr(4); var dx = 0; var dy = 0; switch (direction) { case 'up': dy = -1; break; case 'down': dy = 1; break; case 'left': dx = -1; break; case 'right': dx = 1; break; } event.target._data.x = wrapX(event.target._data.x + dx); event.target._data.y = wrapY(event.target._data.y + dy); sendMapData(event.target._data); break; case 'set ': var value = event.data.substr(4); map[event.target._data.y][event.target._data.x] = value; for (var viewer in viewers) sendMapData(viewers[viewer]); break; case 'txt ': var name = event.target._data.name; var message = event.data.substr(4); for (var viewer in viewers) viewers[viewer].port.postMessage('txt ' + name + ' ' + message); break; case 'msg ': var party1 = event.target._data; var party2 = viewers[event.data.substr(4).split(' ', 1)[0]]; if (party2) { var channel = new MessageChannel(); party1.port.postMessage('msg ' + party2.name, [channel.port1]); party2.port.postMessage('msg ' + party1.name, [channel.port2]); } break; } }
Connecting to multiple pages. The script uses the onconnect event listener to listen for multiple connections.
Direct channels. When the worker receives a "msg" message from one viewer naming another viewer, it sets up a direct connection between the two, so that the two viewers can communicate directly without the worker having to proxy all the messages.
With multicore CPUs becoming prevalent, one way to obtain better performance is to split computationally expensive tasks amongst multiple workers. In this example, a computationally expensive task that is to be performed for every number from 1 to 10,000,000 is farmed out to ten subworkers.
The main page is as follows, it just reports the result:
// settings var num_workers = 10; var items_per_worker = 1000000; // start the workers var result = 0; var pending_workers = num_workers; for (var i = 0; i < num_workers; i += 1) { var worker = new Worker('core.js'); worker.postMessage(i * items_per_worker); worker.postMessage((i+1) * items_per_worker); worker.onmessage = storeResult; } // handle the results function storeResult(event) { result += 1*event.data; pending_workers -= 1; if (pending_workers <= 0) postMessage(result); // finished! }
It consists of a loop to start the subworkers, and then a handler that waits for all the subworkers to respond.
The subworkers are implemented as follows:
var start; onmessage = getStart; function getStart(event) { start = 1*event.data; onmessage = getEnd; } var end; function getEnd(event) { end = 1*event.data; onmessage = null; work(); } function work() { var result = 0; for (var i = start; i < end; i += 1) { // perform some complex calculation here result += 1; } postMessage(result); close(); }
They receive two numbers in two events, perform the computation for the range of numbers thus specified, and then report the result back to the parent.
Suppose that a cryptography library is made available that provides three tasks:
Generate a public/private key pair
Takes a port, on which it will send two messages, first the public key and then the private key.
Given a plaintext and a public key, return the corresponding ciphertext
Takes a port, to which any number of messages can be sent, the first giving the public key, and the remainder giving the plaintext, each of which is encrypted and then sent on that same channel as the ciphertext. The user can close the port when it is done encrypting content.
Given a ciphertext and a private key, return the corresponding plaintext
Takes a port, to which any number of messages can be sent, the first giving the private key, and the remainder giving the ciphertext, each of which is decrypted and then sent on that same channel as the plaintext. The user can close the port when it is done decrypting content.
The library itself is as follows:
function handleMessage(e) { if (e.data == "genkeys") genkeys(e.ports[0]); else if (e.data == "encrypt") encrypt(e.ports[0]); else if (e.data == "decrypt") decrypt(e.ports[0]); } function genkeys(p) { var keys = _generateKeyPair(); p.postMessage(keys[0]); p.postMessage(keys[1]); } function encrypt(p) { var key, state = 0; p.onmessage = function (e) { if (state == 0) { key = e.data; state = 1; } else { p.postMessage(_encrypt(key, e.data)); } }; } function decrypt(p) { var key, state = 0; p.onmessage = function (e) { if (state == 0) { key = e.data; state = 1; } else { p.postMessage(_decrypt(key, e.data)); } }; } // support being used as a shared worker as well as a dedicated worker if ('onmessage' in this) // dedicated worker onmessage = handleMessage; else // shared worker onconnect = function (e) { e.port.onmessage = handleMessage; } // the "crypto" functions: function _generateKeyPair() { return [Math.random(), Math.random()]; } function _encrypt(k, s) { return 'encrypted-' + k + ' ' + s; } function _decrypt(k, s) { return s.substr(s.indexOf(' ')+1); }
Note that the crypto functions here are just stubs and don't do real cryptography.
This library could be used as follows:
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>Worker example: Crypto library</title> <script> const cryptoLib = new Worker('libcrypto-v1.js'); // or could use 'libcrypto-v2.js' function startConversation(source, message) { const messageChannel = new MessageChannel(); source.postMessage(message, [messageChannel.port2]); return messageChannel.port1; } function getKeys() { let state = 0; startConversation(cryptoLib, "genkeys").onmessage = function (e) { if (state === 0) document.getElementById('public').value = e.data; else if (state === 1) document.getElementById('private').value = e.data; state += 1; }; } function enc() { const port = startConversation(cryptoLib, "encrypt"); port.postMessage(document.getElementById('public').value); port.postMessage(document.getElementById('input').value); port.onmessage = function (e) { document.getElementById('input').value = e.data; port.close(); }; } function dec() { const port = startConversation(cryptoLib, "decrypt"); port.postMessage(document.getElementById('private').value); port.postMessage(document.getElementById('input').value); port.onmessage = function (e) { document.getElementById('input').value = e.data; port.close(); }; } </script> <style> textarea { display: block; } </style> </head> <body onload="getKeys()"> <fieldset> <legend>Keys</legend> <p><label>Public Key: <textarea id="public"></textarea></label></p> <p><label>Private Key: <textarea id="private"></textarea></label></p> </fieldset> <p><label>Input: <textarea id="input"></textarea></label></p> <p><button onclick="enc()">Encrypt</button> <button onclick="dec()">Decrypt</button></p> </body> </html>
A later version of the API, though, might want to offload all the crypto work onto subworkers. This could be done as follows:
function handleMessage(e) { if (e.data == "genkeys") genkeys(e.ports[0]); else if (e.data == "encrypt") encrypt(e.ports[0]); else if (e.data == "decrypt") decrypt(e.ports[0]); } function genkeys(p) { var generator = new Worker('libcrypto-v2-generator.js'); generator.postMessage('', [p]); } function encrypt(p) { p.onmessage = function (e) { var key = e.data; var encryptor = new Worker('libcrypto-v2-encryptor.js'); encryptor.postMessage(key, [p]); }; } function encrypt(p) { p.onmessage = function (e) { var key = e.data; var decryptor = new Worker('libcrypto-v2-decryptor.js'); decryptor.postMessage(key, [p]); }; } // support being used as a shared worker as well as a dedicated worker if ('onmessage' in this) // dedicated worker onmessage = handleMessage; else // shared worker onconnect = function (e) { e.ports[0].onmessage = handleMessage };
The little subworkers would then be as follows.
For generating key pairs:
onmessage = function (e) { var k = _generateKeyPair(); e.ports[0].postMessage(k[0]); e.ports[0].postMessage(k[1]); close(); } function _generateKeyPair() { return [Math.random(), Math.random()]; }
For encrypting:
onmessage = function (e) { var key = e.data; e.ports[0].onmessage = function (e) { var s = e.data; postMessage(_encrypt(key, s)); } } function _encrypt(k, s) { return 'encrypted-' + k + ' ' + s; }
For decrypting:
onmessage = function (e) { var key = e.data; e.ports[0].onmessage = function (e) { var s = e.data; postMessage(_decrypt(key, s)); } } function _decrypt(k, s) { return s.substr(s.indexOf(' ')+1); }
Notice how the users of the API don't have to even know that this is happening — the API hasn't changed; the library can delegate to subworkers without changing its API, even though it is accepting data using message channels.
Creating a worker requires a URL to a JavaScript file. The Worker() constructor is invoked with the URL to that file as its only argument; a worker is then created and returned:
var worker = new Worker('helper.js');
If you want your worker script to be interpreted as a module script instead of the default classic script, you need to use a slightly different signature:
var worker = new Worker('helper.js', { type: "module" });
10.1.3.2 Communicating with a dedicated worker
This section is non-normative.
Dedicated workers use MessagePort objects behind the scenes, and thus support all the same features, such as sending structured data, transferring binary data, and transferring other ports.
The implicit MessagePort used by dedicated workers has its port message queue implicitly enabled when it is created, so there is no equivalent to the MessagePortinterface's start() method on the Worker interface.
To send data to a worker, use the postMessage() method. Structured data can be sent over this communication channel. To send ArrayBuffer objects efficiently (by transferring them rather than cloning them), list them in an array in the second argument.
In either case, the data is provided in the event object's data attribute.
To send messages back, you again use postMessage(). It supports the structured data in the same manner.
postMessage(event.data.input, [event.data.input]); // transfer the buffer back
10.1.3.3 Shared workers
This section is non-normative.
Shared workers are identified by the URL of the script used to create it, optionally with an explicit name. The name allows multiple instances of a particular shared worker to be started.
Shared workers are scoped by origin. Two different sites using the same names will not collide. However, if a page tries to use the same shared worker name as another page on the same site, but with a different script URL, it will fail.
Creating shared workers is done using the SharedWorker() constructor. This constructor takes the URL to the script to use for its first argument, and the name of the worker, if any, as the second argument.
var worker = new SharedWorker('service.js');
Communicating with shared workers is done with explicit MessagePort objects. The object returned by the SharedWorker() constructor holds a reference to the port on its port attribute.
worker.port.onmessage = function (event) { ... }; worker.port.postMessage('some message'); worker.port.postMessage({ foo: 'structured', bar: ['data', 'also', 'possible']});
Inside the shared worker, new clients of the worker are announced using the connect event. The port for the new client is given by the event object's source attribute.
onconnect = function (event) { var newPort = event.source; // set up a listener newPort.onmessage = function (event) { ... }; // send a message back to the port newPort.postMessage('ready!'); // can also send structured data, of course };
10.2 Infrastructure
There are two kinds of workers; dedicated workers, and shared workers. Dedicated workers, once created, are linked to their creator; but message ports can be used to communicate from a dedicated worker to multiple other browsing contexts or workers. Shared workers, on the other hand, are named, and once created any script running in the same origin can obtain a reference to that worker and communicate with it.
The name can have different semantics for each subclass of WorkerGlobalScope. For DedicatedWorkerGlobalScope instances, it is simply a developer-supplied name, useful mostly for debugging purposes. For SharedWorkerGlobalScope instances, it allows obtaining a reference to a common shared worker via the SharedWorker()constructor. For ServiceWorkerGlobalScope objects, it doesn't make sense (and as such isn't exposed through the JavaScript API at all).
DedicatedWorkerGlobalScope objects act as if they had an implicit MessagePort associated with them. This port is part of a channel that is set up when the worker is created, but it is not exposed. This object must never be garbage collected before the DedicatedWorkerGlobalScope object.
All messages received by that port must immediately be retargeted at the DedicatedWorkerGlobalScope object.
Returns dedicatedWorkerGlobal's name, i.e. the value given to the Worker constructor. Primarily useful for debugging.
dedicatedWorkerGlobal . postMessage(message [, transfer ])
Clones message and transmits it to the Worker object associated with dedicatedWorkerGlobal. transfer can be passed as a list of objects that are to be transferred rather than cloned.
attribute must return the DedicatedWorkerGlobalScope object's name. Its value represents the name given to the worker using the Worker constructor, used primarily for debugging purposes.
Returns sharedWorkerGlobal's name, i.e. the value given to the SharedWorker constructor. Multiple SharedWorker objects can correspond to the same shared worker (and SharedWorkerGlobalScope), by reusing the same name.
attribute must return the SharedWorkerGlobalScope object's name. Its value represents the name that can be used to obtain a reference to the worker using the SharedWorker constructor.
flag, which must be initially false, but which can get set to true by the algorithms in the processing model section below.
Once the WorkerGlobalScope's closing flag is set to true, the event loop's task queues must discard any further tasks that would be added to them (tasks already on the queue are unaffected except where otherwise specified). Effectively, once the closing flag is true, timers stop firing, notifications for all pending background operations are dropped, etc.
specifies a global object that is a WorkerGlobalScope object (i.e., if we are creating a nested worker), then the relevant owner is that global object. Otherwise,
The second part of this definition allows a shared worker to survive for a short time while a page is loading, in case that page is going to contact the shared worker again. This can be used by user agents as a way to avoid the cost of restarting a shared worker used by a site when the user is navigating from page to page within that site.
Create a separate parallel execution environment (i.e. a separate thread or process or equivalent construct), and run the rest of these steps in that context.
Create a new WorkerLocation object and associate it with
worker global scope
.
Closing orphan workers: Start monitoring the worker such that no sooner than it stops being a protected worker, and no later than it stops being a permissible worker,
Suspending workers: Start monitoring the worker, such that whenever
worker global scope
's closing flag is false and the worker is a suspendable worker, the user agent suspends execution of script in that worker until such time as either the closing flag switches to true or the worker stops being a suspendable worker.
In addition to the usual possibilities of returning a value or failing due to an exception, this could be prematurely aborted by the terminate a worker algorithm defined below.
until it is destroyed.The worker processing model remains on this step until the event loop is destroyed, which happens after the closing flag is set to true, as described in the event loop processing model.
User agents may invoke the terminate a worker algorithm when a worker stops being an active needed worker and the worker continues executing even after its closing flag was set to true.
Whenever an uncaught runtime script error occurs in one of the worker's scripts, if the error did not occur while handling a previous script error, the user agent must report the error for that script, with the position (line number and column number) where the error occurred, using the WorkerGlobalScope object as the target.
For shared workers, if the error is still not handled afterwards, the error may be reported to a developer console.
For dedicated workers, if the error is still not handled afterwards, the user agent must queue a task to run these steps:
is true, then the user agent must act as if the uncaught runtime script error had occurred in the global scope that the Worker object is in, thus repeating the entire runtime script error reporting process one level up.
If the implicit port connecting the worker to its Worker object has been disentangled (i.e. if the parent worker has been terminated), then the user agent must act as if the Worker object had no error event handler and as if that worker's onerror attribute was null, but must otherwise act as described above.
Thus, error reports propagate up to the chain of dedicated workers up to the original Document, even if some of the workers along this chain have been terminated and garbage collected.
Returns a new Worker object. scriptURL will be fetched and executed in the background, creating a new global environment for which worker represents the communication channel. options can be used to define the name of that global environment via the name option, primarily for debugging purposes. It can also ensure this new global environment supports JavaScript modules (specify type: "module"), and if that is specified, can also be used to specify how scriptURL is fetched through the credentials option.
Clones message and transmits it to worker's global environment. transfer can be passed as a list of objects that are to be transferred rather than cloned.
The
terminate()
method, when invoked, must cause the terminate a worker algorithm to be run on the worker with which the object is associated.
Worker objects act as if they had an implicit MessagePort associated with them. This port is part of a channel that is set up when the worker is created, but it is not exposed. This object must never be garbage collected before the Worker object.
All messages received by that port must immediately be retargeted at the Worker object.
The
postMessage()
method on Worker objects must act as if, when invoked, it immediately invoked the method of the same name on the port, with the same arguments, and returned the same return value.
The postMessage() method's first argument can be structured data:
constructor is invoked, the user agent must run the following steps:
The user agent may throw a "SecurityError"DOMException if the request violates a policy decision (e.g. if the user agent is configured to not allow the page to start dedicated workers).
sharedWorker = new SharedWorker(scriptURL [, name ])
Returns a new SharedWorker object. scriptURL will be fetched and executed in the background, creating a new global environment for which sharedWorkerrepresents the communication channel. name can be used to define the name of that global environment.
sharedWorker = new SharedWorker(scriptURL [, options ])
Returns a new SharedWorker object. scriptURL will be fetched and executed in the background, creating a new global environment for which sharedWorkerrepresents the communication channel. options can be used to define the name of that global environment via the name option. It can also ensure this new global environment supports JavaScript modules (specify type: "module"), and if that is specified, can also be used to specify how scriptURL is fetched through the credentials option.
Each user agent has a single shared worker manager for simplicity. Implementations could use one per origin; that would not be observably different and enables more concurrency.
When the
SharedWorker(scriptURL, options)
constructor is invoked:
Optionally, throw a "SecurityError"DOMException if the request violates a policy decision (e.g. if the user agent is configured to not allow the page to start shared workers).
For example, a user agent could have a development mode that isolates a particular top-level browsing context from all other pages, and scripts in that development mode could be blocked from connecting to shared workers running in the normal browser mode.
Support:hardwareconcurrencyChrome for Android64+Chrome37+iOS Safari10.3+UC Browser for Android11.8+Firefox48+IENoneOpera MiniNoneSamsung Internet4+Safari10.1+Edge15+Android Browser62+Opera24+
Returns the number of logical processors potentially available to the user agent.
The
navigator.hardwareConcurrency
attribute's getter must return a number between 1 and the number of logical processors potentially available to the user agent. If this cannot be determined, the getter must return 1.
User agents should err toward exposing the number of logical processors available, using lower values only in cases where there are user-agent specific limits in place (such as a limitation on the number of workers that can be created) or when the user agent desires to limit fingerprinting possibilities.
, with the rethrow errors argument set to true.If an exception was thrown or if the script was prematurely aborted, then abort all these steps, letting the exception or aborting continue to be processed by the calling script.
script
will run until it either returns, fails to parse, fails to catch an exception, or gets prematurely aborted by the terminate a worker algorithm defined above.
Service Workers
is an example of a specification that runs this algorithm with its own options for the perform the fetch hook. [SW]
attribute of the WorkerGlobalScope interface must return an instance of the WorkerNavigator interface, which represents the identity and state of the user agent (the client):
[Exposed=Worker] interface WorkerNavigator {}; WorkerNavigator includes NavigatorID; WorkerNavigator includes NavigatorLanguage; WorkerNavigator includes NavigatorOnLine; WorkerNavigator includes NavigatorConcurrentHardware;