실제 웹 작업자 : 도움이되는 이유와 사용 방법

Javascript는 단일 스레드이며 여러 스크립트를 동시에 실행할 수 없습니다. 따라서 무거운 계산 작업을 실행하면 때때로 페이지가 응답하지 않고 해당 실행이 완료 될 때까지 사용자가 다른 작업을 수행 할 수 없습니다.

예를 들면 :

average = (numbers) => { let startTime = new Date().getTime(); let len = numbers, sum = 0, i; if (len === 0) { return 0; } for (i = 0; i  { alert("Hello World !!"); } /* Paste the above code in browser dev tool console and try to call average(10000) and hello one by one */

위의 예 에서 hello 메소드 전에 average 를 호출하면 페이지가 응답하지 않고 평균 실행 이 완료 될 때까지 Hello 를 클릭 할 수 없습니다 .

먼저 입력으로 10000을 사용하여 average 를 호출 했을 때 ~ 1.82 초가 소요 되었음을 알 수 있습니다 . 이 시간 동안 페이지가 응답하지 않고 hello 버튼을 클릭 할 수 없습니다.

비동기 프로그래밍

자바 스크립트는 개발자가 비동기 코드 를 작성하는 방법을 제공합니다 . 비동기 코드를 작성하면 이벤트 루프에서 나중에 실행되도록 코드의 일부를 "예약"하여 앱 UI가 응답 할 수 있도록하므로 애플리케이션에서 이러한 종류의 문제를 피할 수 있습니다.

비동기 프로그래밍의 좋은 예는 여기 XHR request에서 API를 비동기 적으로 실행하고 응답을 기다리는 동안 다른 코드를 실행할 수 있습니다. 그러나 이것은 대부분 웹 API와 관련된 특정 사용 사례로 제한됩니다.

비동기 코드를 작성하는 또 다른 방법은 setTimeout방법. 경우에 따라 .NET Framework를 사용하여 더 오래 실행되는 계산에서 UI 차단을 해제하여 좋은 결과를 얻을 수 있습니다 setTimeout. 예를 들어, 복잡한 계산을 별도의 setTimeout호출 로 일괄 처리 합니다.

예를 들면 :

average = (numbers) => { let startTime = new Date().getTime(); var len = numbers, sum = 0, i; if (len === 0) { return 0; } let calculateSumAsync = (i) => { if (i  { sum += i; calculateSumAsync(i + 1); }, 0); } else { // The end of the array is reached so we're invoking the alert. let endTime = new Date().getTime(); alert('Average - ', sum / len); } }; calculateSumAsync(0); }; hello = () => { alert('Hello World !!') };

이 예에서는 평균 계산 버튼을 클릭 한 후에도 여전히 Hello 버튼을 클릭 할 수 있음을 알 수 있습니다 (경고 메시지가 표시됨). 이러한 프로그래밍 방식은 확실히 비 차단 적이지만 너무 많은 시간이 걸리며 실제 응용 프로그램에서는 실행 가능하지 않습니다.

여기에서 동일한 입력 10000에 대해 ~ 60 초가 걸렸는데 이는 매우 비효율적입니다.

그렇다면 이러한 문제를 어떻게 효율적으로 해결할 수 있을까요?

대답은 Web Workers입니다.

웹 워커 란 무엇입니까?

자바 스크립트의 웹 워커는 매우 힘들고 시간이 많이 걸리는 작업을 메인 스레드와 분리 된 스레드로 처리 할 수있는 좋은 방법입니다. 백그라운드에서 실행되며 사용자 인터페이스를 방해하지 않고 작업을 수행합니다.

웹 워커는 JavaScript의 일부가 아니며 JavaScript를 통해 액세스 할 수있는 브라우저 기능입니다.

웹 워커는 명명 된 JS 파일을 실행하는 생성자 함수 Worker ()에 의해 생성됩니다 .

// create a dedicated web worker const myWorker = new Worker('worker.js');

지정된 파일이 있으면 비동기 적으로 다운로드되고 그렇지 않은 경우 작업자가 자동으로 실패하므로 404의 경우에도 애플리케이션이 계속 작동합니다.

다음 섹션에서 웹 워커 생성 및 작업에 대해 자세히 알아볼 것입니다.

작업자 스레드에는 자체 컨텍스트가 있으므로 웹 소켓, 인덱싱 된 DB와 같은 작업자 스레드 내에서 선택한 기능에만 액세스 할 수 있습니다.

웹 워커 에는 몇 가지 제한 사항 이 있습니다.

  1. 작업자 내부에서 DOM을 직접 조작 할 수 없습니다.
  2. 작업자 스레드 내에서 창 개체를 사용할 수 없기 때문에 창 개체의 일부 기본 메서드 및 속성을 사용할 수 없습니다.
  3. 작업자 스레드 내부의 컨텍스트 는 사용량에 따라 DedicatedWorkerGlobalScope 또는 SharedWorkerGlobalScope 를 통해 액세스 할 수 있습니다 .

웹 워커의 기능

웹 작업자에는 두 가지 유형이 있습니다.

  1. 전용 웹 작업자 -전용 작업자는 자신을 호출 한 스크립트에서만 액세스 할 수 있습니다.
  2. 공유 웹 작업자 -공유 작업자는 서로 다른 창, iframe 또는 작업자가 액세스하는 경우에도 여러 스크립트에서 액세스 할 수 있습니다.

이 두 가지 유형의 웹 워커에 대해 자세히 알아 보겠습니다.

웹 워커 생성

전용 및 공유 웹 작업자의 생성은 거의 동일합니다.

전담 웹 작업자

  • 새 워커를 만드는 것은 간단합니다. Worker 생성자를 호출하고 워커로 실행하려는 스크립트의 경로를 전달하기 만하면됩니다.
// create a dedicated web worker const myWorker = new Worker('worker.js');

공유 웹 작업자 :

  • 새 공유 워커를 생성하는 것은 전용 워커와 거의 동일하지만 생성자 이름이 다릅니다.
// creating a shared web worker const mySharedWorker = new SharedWorker('worker.js');

주 스레드와 작업자 스레드 간의 통신

주 스레드와 작업자 스레드 간의 통신은 postMessage 메서드와 onmessage 이벤트 처리기 를 통해 발생 합니다.

전담 웹 작업자

전담 웹 작업자의 경우 통신 시스템이 간단합니다. 작업자에게 메시지를 보내고 싶을 때마다 postMessage 메서드를 사용하면됩니다.

(() => { // new worker let myWorker = new Worker('worker.js'); // event handler to recieve message from worker myWorker.onmessage = (e) => { document.getElementById('time').innerHTML = `${e.data.time} seconds`; }; let average = (numbers) => { // sending message to web worker with an argument myWorker.postMessage(numbers); } average(1000); })();

웹 워커 내부에서 다음과 같이 이벤트 핸들러 블록을 작성하여 메시지를 수신 할 때 응답 할 수 있습니다.

onmessage = (e) => { let numbers = e.data; let startTime = new Date().getTime(); let len = numbers, sum = 0, i; if (len === 0) { return 0; } for (i = 0; i < len; i++) { sum += i; } let endTime = new Date().getTime(); postMessage({average: sum / len, time: ((endTime - startTime) / 1000)}) };

onmessage핸들러는 메시지가 수신 될 때마다 몇 가지 코드를 실행할 수 있습니다.

여기서 우리는 숫자의 평균을 계산 한 다음 postMessage()다시 사용 하여 결과를 주 스레드에 다시 게시합니다.

main.js의 6 행에서 볼 수 있듯이 작업자 인스턴스에서 onmessage 이벤트를 사용했습니다. 따라서 작업자 스레드가 postMessage를 사용할 때마다 주 스레드의 onmessage가 트리거됩니다.

  • 공유 웹 작업자

    공유 웹 작업자의 경우 통신 시스템이 약간 다릅니다. 하나의 작업자가 여러 스크립트간에 공유되므로 작업자 인스턴스의 포트 개체를 통해 통신해야합니다. 이는 전용 작업자의 경우 암시 적으로 수행됩니다. 작업자에게 메시지를 보내고 싶을 때마다 postMessage 메서드를 사용해야합니다.

(() => { // new worker let myWorker = new Worker('worker.js'); // event handler to recieve message from worker myWorker.onmessage = (e) => { document.getElementById('time').innerHTML = `${e.data.time} seconds`; }; let average = (numbers) => { // sending message to web worker with an argument myWorker.postMessage(numbers); } average(1000);

웹 워커 ( main-shared-worker.js ) 내부는 약간 복잡합니다. 먼저, onconnect포트에 연결될 때 핸들러를 사용하여 코드를 실행합니다 ( 라인 2 ).

ports이 이벤트 객체 의 속성을 사용하여 포트를 가져와 변수에 저장합니다 ( 4 행 ).

다음으로 message포트에 핸들러를 추가 하여 계산을 수행하고 결과를 다음과 같이 주 스레드 ( 7 행 및 25 행 )에 반환합니다 .

onmessage = (e) => { let numbers = e.data; let startTime = new Date().getTime(); let len = numbers, sum = 0, i; if (len === 0) { return 0; } for (i = 0; i < len; i++) { sum += i; } let endTime = new Date().getTime(); postMessage({average: sum / len, time: ((endTime - startTime) / 1000)}) };

Termination of a web 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:

// terminating a web worker instance myWorker.terminate();

The worker thread is killed immediately without an opportunity to complete its operations.

Spawning of web worker

Workers may spawn more workers if they wish. But they must be hosted within the same origin as the parent page.

Importing Scripts

Worker threads have access to a global function, importScripts(), which lets them import scripts.

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 */

Working Demo

We have discussed some of the approaches above to achieve async programming so that our UI doesn’t get blocked due to any heavy computational task. But there are some limitations to those approaches. So we can use web workers to solve these kind of problems efficiently.

Click here to run this live demo.

Here, you will see 3 sections:

  1. Blocking Code:

    When you click on calculate average, the loader does not display and after some time you see the final result and time taken. This is because as soon as the average method gets called, I have triggered the showLoader method also. But since JS is single threaded, it won’t execute showLoader until the execution of average gets completed. So, you won’t be able to see the loader in this case ever.

  2. Async Code:

    In this I tried to achieve the same functionality by using the setTimeout method and putting every function execution into an event loop. You will see the loader in this case, but the response takes time as compared to the method defined above.

  3. Web worker:

    This is an example of using a web worker. In this you will see the loader as soon as you click on calculate average and you will get a response in the same time as of method 1, for the same number.

You can access the source code for the same here.

Advanced concepts

There are some advanced concepts related to web workers. We won’t be discussing them in detail, but its good to know about them.

  1. Content Security Policy —

    Web workers have their own execution context independent of the document that created them and because of this reason they are not governed by the Content Security Policy of the parent thread/worker.

    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 inherit the content security policy of the document or worker that created it.

  2. Transferring data to and from workers

    Data passed between main and worker thread is copied and 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.

    Browsers implemented Structured Cloning algorithm to achieve this.

  3. Embedded workers —

    You can also embed the code of worker inside a web page (html). For this you need to add a script tag without a src attribute and assign a non-executable MIME type to it, like this:

    embedded worker   // This script WON'T be parsed by JS engines because its MIME type is text/js-worker. var myVar = 'Hello World!'; // worker block function onmessage(e) { // worker code }    

There are a lot of use cases to use web workers in our application. I have just discussed a small scenario. Hope this helps you understand the concept of web workers.

[Links]

Github Repo : //github.com/bhushangoel/webworker-demo-1 Web worker in action : //bhushangoel.github.io/webworker-demo-1/JS demo showcase : //bhushangoel.github.io/

Thank you for reading.

Happy Learning :)

Originally published at www.thehungrybrain.com.