=================================
=================================
=================================
출처: http://blog.naver.com/PostView.nhn?blogId=hihihichoi&logNo=20141585593
▶ 브라우저의 스레드
• 기본적으로 웹 페이지는 하나의 스레드로 처리
• 처리 시간이 오래 걸리는 스크립트의 실행 동안에는 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
=================================
=================================
=================================
출처: https://www.sitepoint.com/javascript-threading-html5-web-workers/
What is a Web Worker?
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:
thread1.js:
self.onmessage = function(e) { self.postMessage("Hello " + e.data); };
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
- message: a description of the error.
pagescript.js:
worker.onerror = function(e) { alert("Error in file: "+e.filename+"nline: "+e.lineno+"nDescription: "+e.message); };
Loading Further JavaScript Files
One or more additional JavaScript libraries can be loaded within a web worker using importScripts()
, e.g.
importScripts("lib1.js", "lib2.js", "lib3.js");
Alternatively, you could load further web workers … but, I’d recommend keeping it simple until browsers catch up with your threading ambitions!
Stopping a Dedicated Web Worker
The web worker thread can be stopped using the close()
method, e.g.
thread1.js:
self.onmessage = function(e) { if (e.data == "STOP!") self.close(); };
This discards any tasks awaiting processing and prevents further events being queued.
That’s all you need to know about dedicated web workers. In my next post, we’ll discuss shared web workers — a more complex beast!
=================================
=================================
=================================
출처: https://developer.mozilla.org/ko/docs/Web/API/Web_Workers_API/basic_usage
웹 워커는 웹 컨텐츠를 위해서 백그라운드 스레드에서 스크립트를 실행할 간편한 방법을 제공합니다. 워커 스레드는 사용자 인터페이스(UI)를 방해하지 않고 작업을 수행할 수 있습니다. 또한 워커는 ( responseXML
과 channel
속성이 언제나 null이지만) XMLHttpRequest
를 사용하여 I/O작업을 수행할 수도 있습니다. 워커는 생성이 된 후에 생성자가 명시한 이벤트 핸들러로 메세지를 올려서 자신의 하위 작업(spawning task)에 메세지를 전달할 수 도 있습니다. 본 글에서 전용 워커와 공유 워커에 대하여 소개합니다.
Web Workers API
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 Message
event'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
.
Dedicated workers
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.
Terminating a worker
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.
Shared workers
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.
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 message
handler 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.
About thread safety
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.
Content security policy
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 details
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:
onmessage = function (oEvent) { postMessage({ "id": oEvent.data.id, "evaluated": eval(oEvent.data.code) }); }
Sample usage:
// asynchronous alert message... asyncEval("3 + 2", function (sMessage) { alert("3 + 2 = " + sMessage); }); // asynchronous print message... asyncEval("\"Hello World!!!\"", function (sHTML) { document.body.appendChild(document.createTextNode(sHTML)); }); // asynchronous void... asyncEval("(function () {\n\tvar oReq = new XMLHttpRequest();\n\toReq.open(\"get\", \"http://www.mozilla.org/\", false);\n\toReq.send(null);\n\treturn oReq.responseText;\n})()");
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:
this.addListeners = function(name, listener){ listeners[name] = listener; } this.removeListeners = function(name){ delete listeners[name]; }
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
.:
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); } }
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 }
And the onmessage
method is now trivial:
onmessage = function(event){ if (event.data instanceof Object && event.data.hasOwnProperty("queryMethod") && event.data.hasOwnProperty("queryMethodArguments")){ queryableFunctions[event.data.queryMethod] .apply(self, event.data.queryMethodArguments); } else { defaultReply(event.data); } }
Here are the full implementation:
example.html (the main page):
<!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]);
Embedded workers
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) }
Further examples
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 HTML code
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Test threads fibonacci</title> </head> <body> <div id="result"></div> <script language="javascript"> var worker = new Worker("fibonacci.js"); worker.onmessage = function(event) { document.getElementById("result").textContent = event.data; dump("Got: " + event.data + "\n"); }; worker.onerror = function(error) { dump("Worker error: " + error.message + "\n"); throw error; }; worker.postMessage("5"); </script> </body> </html>
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.
Performing web I/O in the background
You can find an example of this in the article Using workers in extensions .
Dividing tasks among multiple workers
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.
Other types of worker
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.
Functions and interfaces available in workers
You can use most standard JavaScript features inside a web worker, including:
Navigator
XMLHttpRequest
Array
,Date
,Math
, andString
Window.requestAnimationFrame
,WindowTimers.setTimeout
, andWindowTimers.setInterval
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.
=================================
=================================
=================================
출처: https://www.html5rocks.com/en/tutorials/workers/basics/
The Problem: JavaScript Concurrency
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:
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.
doWork.js (the worker):
self.addEventListener('message', function(e) { self.postMessage(e.data); }, false);
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()
:
worker.postMessage(arrayBuffer, [arrayBuffer]); window.postMessage(arrayBuffer, targetOrigin, [arrayBuffer]);
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 ArrayBuffer
by the way. For example, it can be a JSON object:
worker.postMessage({data: int8View, moreData: anotherBuffer}, [int8View.buffer, anotherBuffer]);
The important point being: the second argument must be an array of ArrayBuffer
s. This is your list of transferrable items.
To see the speed improvement of transferrables, check out this DEMO. For more information on transferrables, see our HTML5Rock post.
The Worker Environment
Worker Scope
In the context of a worker, both self
and this
reference the global scope for the worker. Thus, the previous example could also be written as:
addEventListener('message', function(e) { var data = e.data; switch (data.cmd) { case 'start': postMessage('WORKER STARTED: ' + data.msg); break; case 'stop': ... }, false);
Alternatively, you could set the onmessage
event handler directly (though addEventListener
is always encouraged by JavaScript ninjas).
onmessage = function(e) { var data = e.data; ... };
Features Available to Workers
Due to their multi-threaded behavior, web workers only has access to a subset of JavaScript's features:
- The
navigator
object - The
location
object (read-only) XMLHttpRequest
setTimeout()/clearTimeout()
andsetInterval()/clearInterval()
- The Application Cache
- Importing external scripts using the
importScripts()
method - Spawning other web workers
Workers do NOT have access to:
- The DOM (it's not thread-safe)
- The
window
object - The
document
object - The
parent
object
Loading External Scripts
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:
worker.js:
importScripts('script1.js'); importScripts('script2.js');
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:http://localhost/c745ef73-ece9-46da-8f66-ebes574789b1
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.
workerWithError.js:
self.addEventListener('message', function(e) { postMessage(1/x); // Intentional error. };
A Word on Security
Restrictions with Local Access
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
- Image filtering in <canvas>
- Updating many rows of a local web database
Demos
References
- Web Workers specification
- "Using web workers" from Mozilla Developer Network
- "Web Workers rise up!" from Dev.Opera
=================================
=================================
=================================
출처: http://html5dev.tistory.com/184
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); } |
출처: http://html5dev.tistory.com/184 [HTML5 Developer]
=================================
=================================
=================================
출처: http://deviant86.tistory.com/483
if(window.Worker){
alert("이 브라우저는 웹 워커를 지원합니다")
}else{
alert("이 브라우저는 웹 워커를 지원하지 않습니다")
}
|
ex) var worker = new Worker("worker.js") |
ex) worker.js
onmessage = function(evt){ //메시지 수신
for(var i=evt.data; i<1000000; i++){
postMessage(i); //1씩 증가한 값을 호출한 곳으로 전달
}
}; |
<!DOCTYPE HTML>
<HTML>
<HEAD>
<meta charset="utf-8">
<TITLE>Web Worker example</TITLE>
</HEAD>
<BODY>
<p>
<input id="start" type="button" value="start work" onclick="startWork();"/>
<input id="stop" type="button" value="stop working" onclick="stopWork();"/>
</p>
<p id="process">before</p>
<textarea rows="4" cols="60">write...</textarea>
</BODY>
<script>
if(window.Worker){
alert("이 브라우저는 웹 워커를 지원합니다")
}else{
alert("이 브라우저는 웹 워커를 지원하지 않습니다")
}
var worker;
function makeWorker(){ //worker create
worker = new Worker("./worker.js");
}
function startWork(){
makeWorker(); //call function
worker.postMessage(0); //send value '0' to worker
worker.onmessage = function(evt){ //receive result message from worker
//print message document.getElementById("process").innerHTML = evt.data;
};
}
function stopWork(){
if(worker){
worker.terminate(); //stop working
alert("worker stopped");
}
}
worker.onerror = function(evt){
document.getElementById("process").innerHTML = "error occurred!";
}
</script>
</HTML> |
출처: http://deviant86.tistory.com/483 [Cocooning_]
=================================
=================================
=================================
출처: http://yoonnamee.tistory.com/132 [yoon's]
[ch12-webworker]
01.html
<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();
});
});
</script>
</head>
<body>
<!-- 웹워커: 자바스크립트 코드를 백그라운드에서 실행시키는 기술 -->
<input type="text" id="guguNum">
<input type="button" id="btnOk" value="확인">
<input type="button" id="btnStop" value="중지">
<br>
<div id="result"></div>
</body>
worker1.js (new > 자바스크립트 소스파일)
//포그라운드에서 데이터가 도착
onmessage= function(event) {
//백그라운드로 처리될 코드작성
var result = "";
var dan = event.data;//인자로 넘어온 데이터 호출
for(var i=0;i<100000;i++){
result += dan + '*' + i + '=' + dan*i + '<br>';
}
//ui쪽에 데이터 전달
postMessage(result);
};
출처: http://yoonnamee.tistory.com/132 [yoon's]
=================================
=================================
=================================
출처: http://imaitman.tistory.com/232
'웹워커'란 JavaScript 코드를 백그라운드에서 실행시키기 위한 기술입니다.
처리 시간이 오래 걸리는 스크립트를 실행하면 브라우져가 멈춘것 처럼 보이거나 브라우져에서
스크립트 실행여부를 묻는 창이 뜨기도 하는데 이런 로직을 '웹워커'로 구현하면
UI 스레드와는 별개로 백그라운드에서 실행 시킬 수 있습니다.
하지만 '웹워커'는 워커를 생성시키는 메인 스레드와는 별개로 구동되기 때문에 window나
document객체에는 접근할 수 없습니다. 다시 말해 DOM에 접근할 수 없다는 의미가 됩니다.
* 워크 생성
var worker = new Worker('worker.js');
* 워커쪽으로 메세지 보내기
worker.postMessage('Hello');
* 워커에서 메세지 받기
:워커쪽에서는 onmessage이벤트 핸들러를 지정합니다.
// 워커쪽 처리
onmessage = function(e) {
// 메세지 처리 수행
}
* 워커를 생성한 곳으로 메세지 보내기
// 워커쪽 처리
onmessage = function(e) {
postMessage('Echo from worker : ' + e.data);
}
worker.onmessage = function(e) {
alert(e.data);
}
===============================================
<!DOCTYPE html>
<html>
<body>
Number : <input type='text' id='num'>
<button onclick='calculate()'>Calculate</button>
<button onclick='stop()'>Stop</button>
<script>
var w;
if(typeof(Worker)!=="undefined") {
alert("This browser supports the Web worker.");
} else {
alert("This browser doesn't support the Web worker.");
}
function calculate() {
// 실행 중인 워커가 있으면 정지
if (w) {
w.terminate();
}
var num = document.getElementById('num').value;
w = new Worker('worker.js');
// 워커로부터 메세지 수신 이벤트 핸들러
w.onmessage = function(event) {
alert('Sum is ' + event.data);
};
// 워커로 메세지 송신
w.postMessage(num);
}
function stop() {
// 실행 중인 워커가 있으면 정지
if (w) {
w.terminate();
}
alert('Stopped.');
}
</script>
</body>
</html>
출처: http://imaitman.tistory.com/232 [To be rich..]
=================================
=================================
=================================
출처: https://johnresig.com/blog/web-workers/
Computing with JavaScript Web Workers
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:
new Worker( "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 aresetTimeout
,setInterval
, andXMLHttpRequest
.) - 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:
var worker = new Worker( "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() } }; function done(){ // 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.)
Check out the demo and source:
I ran the old implementation against the new one in the browsers that support Web Workers to arrive at the following results:
Browser | Old Runs/s | New Runs/s |
---|---|---|
Firefox 3.5 | 2700 | 4600 |
Safari 4 | 2500 | 8400 |
Chrome Nightly | 4500 | 9600 |
How does this implementation work? Digging in to the source of the parent launcherwe can see:
// 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.
=================================
=================================
=================================
출처: https://developer.mozilla.org/ko/docs/Web/API/SharedWorker
Shared Worker
SharedWorker 인터페이스는 윈도우 창이나 iframe, 워커등의 다른 브라우징 컨텍스트에서도 접근이 가능한 특정 종류의 워커를 의미합니다. 기존의 다른 종류의 워커들과 다른 전역 스코프를 갖는 인터페이스를 구현합니다.
SharedWorkerGlobalScope
.
속성들
EventTarget
의 속성들을 상속 받습니다. 그리고 AbstractWorker
의 속성들을 구현할 수 있습니다.
- 워커에서
ErrorEvent
타입의 에러가 발생했을 때 호출되는는EventListener
SharedWorker.port
Read only- shared worker를 제어하거나 통신하기 위해 사용되는
MessagePort
객체를 반환
생성자
SharedWorker()
- 특정 URL에서 스크립트를 실행하는 shared web worker를 생성합니다.
메서드
EventTarget
의 속성들을 상속 받습니다. 그리고 AbstractWorker
의 속성들을 구현할 수 있습니다.
예제
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'); }
워커에서 SharedWorkerGlobalScope.onconnect
핸들러를 이용하여 위에 언급된 포트에 접속할 수 있습니다. 워커에 연관되어 있는 포트는 connect
이벤트 포트 속성에 접근할 수 있습니다 — 그리고나서 MessagePort
start() 메서드로 포트를 시작하고, onmessage 핸들러로 메인쓰레드에서 받은 메시지를 처리합니다.
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 onmessage
event 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.
10.1.2.2 Using a JavaScript module as a worker
This section is non-normative.
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.
The main page is as follows:
<!DOCTYPE html> <meta charset="utf-8"> <title>Worker example: image decoding</title> <p> <label> Type an image URL to decode <input type="url" id="image-url" list="image-list"> <datalist id="image-list"> <option value="https://html.spec.whatwg.org/images/drawImage.png"> <option value="https://html.spec.whatwg.org/images/robots.jpeg"> <option value="https://html.spec.whatwg.org/images/arcTo2.png"> </datalist> </label> </p> <p> <label> Choose a filter to apply <select id="filter"> <option value="none">none</option> <option value="grayscale">grayscale</option> <option value="brighten">brighten by 20%</option> </select> </label> </p> <canvas id="output"></canvas> <script type="module"> const worker = new Worker("worker.js", { type: "module" }); worker.onmessage = receiveFromWorker; const url = document.querySelector("#image-url"); const filter = document.querySelector("#filter"); const output = document.querySelector("#output"); url.oninput = updateImage; filter.oninput = sendToWorker; let imageData, context; function updateImage() { const img = new Image(); img.src = url.value; img.onload = () => { output.innerHTML = ""; const canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; context = canvas.getContext("2d"); context.drawImage(img, 0, 0); imageData = context.getImageData(0, 0, canvas.width, canvas.height); sendToWorker(); output.appendChild(canvas); }; } function sendToWorker() { worker.postMessage({ imageData, filter: filter.value }); } function receiveFromWorker(e) { context.putImageData(e.data, 0, 0); } </script>
The worker file is then:
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; } };
10.1.2.3 Shared workers introduction
This section is non-normative.
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.
Here is the outer HTML page:
<!DOCTYPE HTML> <meta charset="utf-8"> <title>Shared workers: demo 3</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(); worker.port.postMessage('ping'); </script> <iframe src="inner.html"></iframe>
Here is the inner HTML page:
<!DOCTYPE HTML> <meta charset="utf-8"> <title>Shared workers: demo 3 inner frame</title> <pre id=log>Inner log:</pre> <script> var worker = new SharedWorker('test.js'); var log = document.getElementById('log'); worker.port.onmessage = function(e) { log.textContent += '\n' + e.data; } </script>
Here is the JavaScript worker:
var count = 0; onconnect = function(e) { count += 1; var port = e.ports[0]; port.postMessage('Hello World! You are connection #' + count); port.onmessage = function(e) { port.postMessage('pong'); } }
10.1.2.4 Shared state using a shared worker
This section is non-normative.
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.
10.1.2.5 Delegation
This section is non-normative.
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:
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>Worker example: Multicore computation</title> </head> <body> <p>Result: <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 itself is as follows:
// 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.
10.1.2.6 Providing libraries
This section is non-normative.
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.
10.1.3 Tutorials
10.1.3.1 Creating a dedicated worker
This section is non-normative.
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.
To receive messages from a dedicated worker, use the onmessage
event handler IDL attribute on the Worker
object:
worker.onmessage = function (event) { ... };
You can also use the addEventListener()
method.
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 MessagePort
interface'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.
worker.postMessage({ operation: 'find-edges', input: buffer, // an ArrayBuffer object threshold: 0.6, }, [buffer]);
To receive a message inside the worker, the onmessage
event handler IDL attribute is used.
onmessage = function (event) { ... };
You can again also use the addEventListener()
method.
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.
10.2.1 The global scope
The global scope is the "inside" of a worker.
10.2.1.1 The WorkerGlobalScope
common interface
[Exposed=Worker] interface WorkerGlobalScope : EventTarget { readonly attribute WorkerGlobalScope self; readonly attribute WorkerLocation location; readonly attribute WorkerNavigator navigator; void importScripts(USVString... urls); attribute OnErrorEventHandler onerror; attribute EventHandler onlanguagechange; attribute EventHandler onoffline; attribute EventHandler ononline; attribute EventHandler onrejectionhandled; attribute EventHandler onunhandledrejection; };
WorkerGlobalScope
serves as the base class for specific types of worker global scope objects, including DedicatedWorkerGlobalScope
, SharedWorkerGlobalScope
, and ServiceWorkerGlobalScope
.
A WorkerGlobalScope
object has an associated
owner set
(a set of Document
and WorkerGlobalScope
objects). It is initially empty and populated when the worker is created or obtained.
It is a set, instead of a single owner, to accomodate SharedWorkerGlobalScope
objects.
A WorkerGlobalScope
object has an associated
worker set
(a set of WorkerGlobalScope
objects). It is initially empty and populated when the worker creates or obtains further workers.
A WorkerGlobalScope
object has an associated
type
("classic
" or "module
"). It is set during creation.
A WorkerGlobalScope
object has an associated
url
(null or a URL). It is initially null.
A WorkerGlobalScope
object has an associated
name
(a string). It is set during creation.
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).
A WorkerGlobalScope
object has an associated
HTTPS state
(an HTTPS state value). It is initially "none
".
A WorkerGlobalScope
object has an associated
referrer policy
(a referrer policy). It is initially the empty string.
A WorkerGlobalScope
object has an associated
CSP list
, which is a CSP list containing all of the Content Security Policy objects active for the worker. It is initially an empty list.
A WorkerGlobalScope
object has an associated
module map
. It is a module map, initially empty.
- workerGlobal .
self
- Returns workerGlobal.
- workerGlobal .
location
- Returns workerGlobal's
WorkerLocation
object. - workerGlobal .
navigator
- Returns workerGlobal's
WorkerNavigator
object. - workerGlobal .
importScripts
(urls...) - Fetches each URL in urls, executes them one-by-one in the order they are passed, and then returns (or throws if something went amiss).
The
self
attribute must return the WorkerGlobalScope
object itself.
The
location
attribute must return the WorkerLocation
object whose associated WorkerGlobalScope
object is the WorkerGlobalScope
object.
While the WorkerLocation
object is created after the WorkerGlobalScope
object, this is not problematic as it cannot be observed from script.
The following are the event handlers (and their corresponding event handler event types) that must be supported, as event handler IDL attributes, by objects implementing the WorkerGlobalScope
interface:
10.2.1.2 Dedicated workers and the DedicatedWorkerGlobalScope
interface
[Global=(Worker,DedicatedWorker),Exposed=DedicatedWorker] interface DedicatedWorkerGlobalScope : WorkerGlobalScope { [Replaceable] readonly attribute DOMString name; void postMessage(any message, optional sequence<object> transfer = []); void close(); attribute EventHandler onmessage; attribute EventHandler onmessageerror; };
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.
- dedicatedWorkerGlobal .
name
- 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. - dedicatedWorkerGlobal .
close
() - Aborts dedicatedWorkerGlobal.
The
name
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.
The
postMessage()
method on DedicatedWorkerGlobalScope
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.
To
close a worker
, given a
workerGlobal
, run these steps:
- Discard any tasks that have been added to
workerGlobal's event loop's task queues.
- Set
workerGlobal's closing flag to true. (This prevents any further tasks from being queued.)
The
close()
method, when invoked, must close a worker with this DedicatedWorkerGlobalScope
object.
The following are the event handlers (and their corresponding event handler event types) that must be supported, as event handler IDL attributes, by objects implementing the DedicatedWorkerGlobalScope
interface:
For the purposes of the application cache networking model, a dedicated worker is an extension of the cache host from which it was created.
10.2.1.3 Shared workers and the SharedWorkerGlobalScope
interface
[Global=(Worker,SharedWorker),Exposed=SharedWorker] interface SharedWorkerGlobalScope : WorkerGlobalScope { [Replaceable] readonly attribute DOMString name; void close(); attribute EventHandler onconnect; };
A SharedWorkerGlobalScope
object has an associated
constructor origin
, and
constructor url
. They are initialized when the SharedWorkerGlobalScope
object is created, in the run a worker algorithm.
Shared workers receive message ports through connect
events on their SharedWorkerGlobalScope
object for each connection.
- sharedWorkerGlobal .
name
- Returns sharedWorkerGlobal's name, i.e. the value given to the
SharedWorker
constructor. MultipleSharedWorker
objects can correspond to the same shared worker (andSharedWorkerGlobalScope
), by reusing the same name. - sharedWorkerGlobal .
close
() - Aborts sharedWorkerGlobal.
The
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.
The
close()
method, when invoked, must close a worker with this SharedWorkerGlobalScope
object.
The following are the event handlers (and their corresponding event handler event types) that must be supported, as event handler IDL attributes, by objects implementing the SharedWorkerGlobalScope
interface:
10.2.2 The event loop
Each WorkerGlobalScope
object has a distinct event loop, separate from those used by units of related similar-origin browsing contexts. This event loop has no associatedbrowsing context, and its task queues only have events, callbacks, and networking activity as tasks. These event loops are created by the run a worker algorithm.
Each WorkerGlobalScope
object also has a
closing
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.
10.2.3 The worker's lifetime
Workers communicate with other workers and with browsing contexts through message channels and their MessagePort
objects.
Each WorkerGlobalScope
object
worker global scope
has a list of
the worker's ports
, which consists of all the MessagePort
objects that are entangled with another port and that have one (but only one) port owned by
worker global scope
. This list includes the implicit MessagePort
in the case of dedicated workers.
Given an environment settings object
o
when creating or obtaining a worker, the
relevant owner to add
depends on the type of global object specified by
o
. If
o
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,
o
specifies a global objectthat is a Window
object, and the relevant owner is the responsible document specified by
o
.
A worker is said to be a
permissible worker
if its WorkerGlobalScope
's owner set is not empty or:
- its owner set has been empty for no more than a short user-agent-defined timeout value,
- its
WorkerGlobalScope
object is aSharedWorkerGlobalScope
object (i.e., the worker is a shared worker), and - the user agent has a browsing context whose
Document
object is not completely loaded.
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.
A worker is said to be an
active needed worker
if any its owners are either Document
objects that are fully active or active needed workers.
A worker is said to be a
protected worker
if it is an active needed worker and either it has outstanding timers, database transactions, or network connections, or its list of the worker's ports is not empty, or its WorkerGlobalScope
is actually a SharedWorkerGlobalScope
object (i.e. the worker is a shared worker).
A worker is said to be a
suspendable worker
if it is not an active needed worker but it is a permissible worker.
10.2.4 Processing model
When a user agent is to
run a worker
for a script with Worker
or SharedWorker
object
worker
, URL
url
outside settings
outside port
, and a WorkerOptions
dictionary
options
, it must run the following steps.
- 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.
- For the purposes of timing APIs, this is the
official moment of creationof the worker.
- Let
is sharedbe true ifworkeris a
SharedWorker
object, and false otherwise. - Let
ownerbe the relevant owner to add givenoutside settings.
- Let
parent worker global scopebe null.
- If
owneris a
WorkerGlobalScope
object (i.e., we are creating a nested worker), then setparent worker global scopetoowner. - Call the JavaScript InitializeHostDefinedRealm() abstract operation with the following customizations:
- For the global object, if
is sharedis true, create a new
SharedWorkerGlobalScope
object. Otherwise, create a newDedicatedWorkerGlobalScope
object.
- For the global object, if
- Let
realm execution contextbe the running JavaScript execution context.
- This is the JavaScript execution context created in the previous step.
- Let
worker global scopebe the global object ofrealm execution context's Realm component.
- This is the
DedicatedWorkerGlobalScope
orSharedWorkerGlobalScope
object created when calling InitializeHostDefinedRealm. - Set up a worker environment settings object with
realm execution contextandoutside settings, and letinside settingsbe the result.
- Set
worker global scope's name to the value ofoptions's
name
member. - If
is sharedis true, then:
- Set
worker global scope's constructor origin tooutside settings's origin.
- Set
worker global scope's constructor url tourl.
- Set
- Let
destinationbe "
sharedworker
" ifis sharedis true, and "worker
" otherwise. - Obtain
scriptby switching on the value ofoptions's
type
member:- "
classic
" - Fetch a classic worker script given url, outside settings, destination, and inside settings.
- "
module
" - Fetch a module worker script graph given url, outside settings, destination, the value of the
credentials
member of options, and inside settings.
request, perform the following steps if the flag is set:- Set request's reserved client to inside settings.
- Fetch
request, and asynchronously wait to run the remaining steps as part of fetch's process response for the responseresponse.
- Set
worker global scope's url toresponse's url.
- Set
worker global scope's HTTPS state toresponse's HTTPS state.
- Set
worker global scope's referrer policy to the result of parsing the `
Referrer-Policy
` header ofresponse. - Execute the Initialize a
global object
's CSP list algorithm onworker global scopeandresponse. [CSP] - Asynchronously complete the perform the fetch steps with
response.
error
atworker, and return. Otherwise, continue the rest of these steps after the algorithm's asynchronous completion, withscriptbeing the asynchronous completion value. - "
- Associate
workerwithworker global scope.
- Create a new
MessagePort
object whose owner isinside settings. Letinside portbe this new object. - Associate
inside portwithworker global scope.
- Entangle
outside portandinside port.
- Append
ownertoworker global scope's owner set.
- If
parent worker global scopeis not null, then appendworker global scopetoparent worker global scope's worker set.
- Set
worker global scope's type to the value of the
type
member ofoptions. - Create a new
WorkerLocation
object and associate it withworker 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,
worker global scope's closing flag is set to true.
- 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.
- Set
inside settings's execution ready flag.
- If
scriptis a classic script, then run the classic scriptscript. Otherwise, it is a module script; run the module scriptscript.
- 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.
- Enable
outside port's port message queue.
- If
is sharedis false, enable the port message queue of the worker's implicit port.
- If
is sharedis true, then queue a task, using the DOM manipulation task source, to fire an event named
connect
atworker global scope, usingMessageEvent
, with thedata
attribute initialized to the empty string, theports
attribute initialized to a new frozen array containinginside port, and thesource
attribute initialized toinside port. - Enable the client message queue of the
ServiceWorkerContainer
object whose associated service worker client isworker global scope's relevant settings object. - Event loop: Run the responsible event loop specified by
inside settingsuntil 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.
- The handling of events or the execution of callbacks by tasks run by the event loop might get prematurely aborted by the terminate a worker algorithm defined below.
- Empty the
worker global scope's list of active timers.
- Disentangle all the ports in the list of the worker's ports.
- Empty
worker global scope's owner set.
When a user agent is to
terminate a worker
it must run the following steps in parallel with the worker's main loop (the "run a worker" processing model defined above):
- Set the worker's
WorkerGlobalScope
object's closing flag to true. - If there are any tasks queued in the
WorkerGlobalScope
object's event loop's task queues, discard them without processing them. - Abort the script currently running in the worker.
- If the worker's
WorkerGlobalScope
object is actually aDedicatedWorkerGlobalScope
object (i.e. the worker is a dedicated worker), then empty the port message queue of the port that the worker's implicit port is entangled with.
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.
The task source for the tasks mentioned above is the DOM manipulation task source.
10.2.5 Runtime script errors
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:
- Let
notHandledbe the result of firing an event named
error
at theWorker
object associated with the worker, usingErrorEvent
, with thecancelable
attribute initialized to true, themessage
,filename
,lineno
, andcolno
attributes initialized appropriately, and theerror
attribute initialized to null. - If
notHandledis 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.
The task source for the task mentioned above is the DOM manipulation task source.
10.2.6 Creating workers
10.2.6.1 The AbstractWorker
mixin
interface mixin AbstractWorker { attribute EventHandler onerror; };
The following are the event handlers (and their corresponding event handler event types) that must be supported, as event handler IDL attributes, by objects implementing the AbstractWorker
interface:
10.2.6.2 Script settings for workers
When the user agent is required to
set up a worker environment settings object
, given a JavaScript execution context
execution context
and environment settings object
outside settings
, it must run the following steps:
- Let
inherited responsible browsing contextbeoutside settings's responsible browsing context.
- Let
inherited originbeoutside settings's origin.
- Let
worker event loopbe a newly created event loop.
- Let
realmbe the value ofexecution context's Realm component.
- Let
worker global scopeberealm's global object.
- Let
settings objectbe a new environment settings object whose algorithms are defined as follows:
- The realm execution context
-
Return
execution context.
- The module map
-
Return
worker global scope's module map.
- The responsible browsing context
-
Return
inherited responsible browsing context.
- The responsible event loop
-
Return
worker event loop.
- The responsible document
-
Not applicable (the responsible event loop is not a browsing context event loop).
- The API URL character encoding
-
Return UTF-8.
- The API base URL
-
Return
worker global scope's url.
- The origin
-
Return a unique opaque origin if
worker global scope's url's scheme is "
inherited origindata
", andotherwise.
- The HTTPS state
-
Return
worker global scope's HTTPS state.
- The referrer policy
-
Return
worker global scope's referrer policy.
- Set
settings object's id to a new unique opaque string,settings object's creation URL toworker global scope's url,settings object's target browsing context to null, andsettings object's active service worker to null.
- Set
realm's [[HostDefined]] field tosettings object.
- Return
settings object.
10.2.6.3 Dedicated workers and the Worker
interface
[Constructor(USVString scriptURL, optional WorkerOptions options), Exposed=(Window,Worker)] interface Worker : EventTarget { void terminate(); void postMessage(any message, optional sequence<object> transfer = []); attribute EventHandler onmessage; attribute EventHandler onmessageerror; }; dictionary WorkerOptions { WorkerType type = "classic"; RequestCredentials credentials = "omit"; // credentials is only used if type is "module" DOMString name = ""; }; enum WorkerType { "classic", "module" }; Worker includes AbstractWorker;
- worker = new
Worker
(scriptURL [, options ]) - 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 thename
option, primarily for debugging purposes. It can also ensure this new global environment supports JavaScript modules (specifytype: "module"
), and if that is specified, can also be used to specify how scriptURL is fetched through thecredentials
option. - worker .
terminate
() - Aborts worker's associated global environment.
- worker .
postMessage
(message [, transfer ]) - 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:
worker.postMessage({opcode: 'activate', device: 1938, parameters: [23, 102]});
The following are the event handlers (and their corresponding event handler event types) that must be supported, as event handler IDL attributes, by objects implementing the Worker
interface:
When the
Worker(scriptURL, options)
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). - Let
outside settingsbe the current settings object.
- Parse the
scriptURLargument relative tooutside settings.
- If this fails, throw a "
SyntaxError
"DOMException
. - Let
worker URLbe the resulting URL record.
- Any same-origin URL (including
blob:
URLs) can be used.data:
URLs can also be used, but they create a worker with an opaque origin. - Let
workerbe a new
Worker
object. - Create a new
MessagePort
object whose owner isoutside settings. Let this be theoutside port. - Associate the
outside portwithworker.
- Run this step in parallel:
- Run a worker given
worker,worker URL,outside settings,outside port, andoptions.
- Run a worker given
- Return
worker.
10.2.6.4 Shared workers and the SharedWorker
interface
[Constructor(USVString scriptURL, optional (DOMString or WorkerOptions) options), Exposed=(Window,Worker)] interface SharedWorker : EventTarget { readonly attribute MessagePort port; }; SharedWorker includes AbstractWorker;
- 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 thename
option. It can also ensure this new global environment supports JavaScript modules (specifytype: "module"
), and if that is specified, can also be used to specify how scriptURL is fetched through thecredentials
option. - sharedWorker .
port
- Returns sharedWorker's
MessagePort
object which can be used to communicate with the global environment.
The
port
attribute must return the value it was assigned by the object's constructor. It represents the MessagePort
for communicating with the shared worker.
A user agent has an associated
shared worker manager
which is the result of starting a new parallel queue.
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). - If
optionsis a
DOMString
, setoptionsto a newWorkerOptions
dictionary whosename
member is set to the value ofoptionsand whose other members are set to their default values. - Let
outside settingsbe the current settings object.
- Parse
scriptURLrelative tooutside settings.
- If this fails, throw a "
SyntaxError
"DOMException
. - Otherwise, let
urlRecordbe the resulting URL record.
- Any same-origin URL (including
blob:
URLs) can be used.data:
URLs can also be used, but they create a worker with an opaque origin. - Let
workerbe a new
SharedWorker
object. - Create a new
MessagePort
object whose owner isoutside settings. Let this be theoutside port. - Assign
outside portto the
port
attribute ofworker. - Let
callerIsSecureContextbe the result of executing Is environment settings object a secure context? onoutside settings.
- Enqueue the following steps to the shared worker manager:
- Let
worker global scopebe null.
- If there exists a
SharedWorkerGlobalScope
object whose closing flag is false, constructor origin is same origin withoutside settings's origin, constructor url equalsurlRecord, and name equals the value ofoptions'sname
member, then setworker global scopeto thatSharedWorkerGlobalScope
object. data:
URLs create a worker with an opaque origin. Both the constructor origin and constructor url are compared so the samedata:
URL can be used within an origin to get to the sameSharedWorkerGlobalScope
object, but cannot be used to bypass the same origin restriction.- If
worker global scopeis not null, but the user agent has been configured to disallow communication between the worker represented by theworker global scopeand the scripts whose settings object isoutside settings, then setworker global scopeto null.
- 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.
- If
worker global scopeis not null, then run these subsubsteps:
- Let
settings objectbe the relevant settings object forworker global scope.
- Let
workerIsSecureContextbe the result of executing Is environment settings object a secure context? onsettings object.
- If
workerIsSecureContextis notcallerIsSecureContext, then queue a task to fire an event named
error
atworkerand abort these subsubsteps. [SECURE-CONTEXTS] - Associate
workerwithworker global scope.
- Create a new
MessagePort
object whose owner issettings object. Let this be theinside port. - Entangle
outside portandinside port.
- Queue a task, using the DOM manipulation task source, to fire an event named
connect
atworker global scope, usingMessageEvent
, with thedata
attribute initialized to the empty string, theports
attribute initialized to a new frozen array containing onlyinside port, and thesource
attribute initialized toinside port. - Append the relevant owner to add given
outside settingstoworker global scope's owner set.
- If
outside settings's global object is a
WorkerGlobalScope
object, then appendworker global scopetooutside settings's global object's worker set.
- Let
- Otherwise, in parallel, run a worker given
worker,urlRecord,outside settings,outside port, andoptions.
- Let
- Return
worker.
10.2.7 Concurrent hardware capabilities
interface mixin NavigatorConcurrentHardware { readonly attribute unsigned long long hardwareConcurrency; };
- self .
navigator
.hardwareConcurrency
-
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.
10.3 APIs available to workers
10.3.1 Importing scripts and libraries
When a script invokes the
importScripts(urls)
method on a WorkerGlobalScope
object, the user agent must import scripts into worker global scope given this WorkerGlobalScope
object and
urls
.
To
import scripts into worker global scope
, given a WorkerGlobalScope
object
worker global scope
and a sequence<DOMString>
urls
, run these steps. The algorithm may optionally be customized by supplying custom perform the fetch hooks, which if provided will be used when invoking fetch a classic worker-imported script.
- If
worker global scope's type is "
module
", throw aTypeError
exception. - Let
settings objectbe the current settings object.
- If
urlsis empty, return.
- Parse each value in
urlsrelative tosettings object. If any fail, throw a "
SyntaxError
"DOMException
. - For each
urlin the resulting URL records, run these substeps:
- Fetch a classic worker-imported script given
urlandsettings object, passing along any custom perform the fetch steps provided. If this succeeds, letscriptbe the result. Otherwise, rethrow the exception.
- Run the classic script
script, 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.
-
scriptwill 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.
- Fetch a classic worker-imported script given
Service Workers
is an example of a specification that runs this algorithm with its own options for the perform the fetch hook. [SW]
10.3.2 The WorkerNavigator
interface
The
navigator
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;
10.3.3 The WorkerLocation
interface
[Exposed=Worker] interface WorkerLocation { stringifier readonly attribute USVString href; readonly attribute USVString origin; readonly attribute USVString protocol; readonly attribute USVString host; readonly attribute USVString hostname; readonly attribute USVString port; readonly attribute USVString pathname; readonly attribute USVString search; readonly attribute USVString hash; };
A WorkerLocation
object has an associated
WorkerGlobalScope
object
(a WorkerGlobalScope
object).
The
href
attribute's getter must return the associated WorkerGlobalScope
object's url, serialized.
The
origin
attribute's getter must return the serialization of the associated WorkerGlobalScope
object's url's origin.
The
protocol
attribute's getter must return the associated WorkerGlobalScope
object's url's scheme, followed by ":
".
The
host
attribute's getter must run these steps:
- Let
urlbe the associated
WorkerGlobalScope
object's url. - If
url's host is null, return the empty string.
- If
url's port is null, returnurl's host, serialized.
- Return
url's host, serialized, followed by "
:
" andurl's port, serialized.
The
hostname
attribute's getter must run these steps:
- Let
hostbe the associated
WorkerGlobalScope
object's url's host. - If
hostis null, return the empty string.
- Return
host, serialized.
The
port
attribute's getter must run these steps:
- Let
portbe the associated
WorkerGlobalScope
object's url's port. - If
portis null, return the empty string.
- Return
port, serialized.
The
pathname
attribute's getter must run these steps:
- Let
urlbe the associated
WorkerGlobalScope
object's url. - If
url's cannot-be-a-base-URL flag is set, return the first string inurl's path.
- Return "
/
", followed by the strings inurl's path (including empty strings), separated from each other by "/
".
The
search
attribute's getter must run these steps:
- Let
querybe the associated
WorkerGlobalScope
object's url's query. - If
queryis either null or the empty string, return the empty string.
- Return "
?
", followed byquery.
The
hash
attribute's getter must run these steps:
- Let
fragmentbe the associated
WorkerGlobalScope
object's url's fragment. - If
fragmentis either null or the empty string, return the empty string.
- Return "
#
", followed byfragment.
=================================
=================================
=================================
참고링크:
http://boxfoxs.tistory.com/294
https://www.slideshare.net/jidolstar/html5-web-worker
http://writingdeveloper.tistory.com/222
=================================
=================================
=================================
'WEB > JavaScript' 카테고리의 다른 글
[JavaScript] 자바스크립트 Array 관련 (0) | 2017.11.07 |
---|---|
[JavaScript] 자바스크립트 Map 관련 (0) | 2017.11.07 |
html5 자바스크립트 canvas 풀스크린 스케일링 확대 관련 (2) | 2016.06.21 |
자바스크립트 객체화 캡슐 private public 모음 관련 (0) | 2016.06.21 |
자바스크립트의 encodeURI 관련 (2) | 2016.06.03 |
댓글 영역