JavaScriptはシングルスレッドで実行されますが、Web Workerを利用することでマルチスレッド化することが可能です。簡単に調べてみましたのでアウトプットしてみます。
そもそもマルチスレッドって何?
基本的にJavaScriptという言語はプログラムが実行されている時、一瞬一瞬を時間単位で切り取ると一つの処理の実行しかしていません。
これをシングルスレッドと呼ぶのですが、これに対しマルチスレッドと呼ばれるものによって、一瞬に対し複数の処理を実行することができます。
JavaScriptにおいてはWeb Workerという機能を用いてこれを実装することができます。
イメージで例えてみると、
あるところにJavaScriptプログラムの実行処理に明け暮れていたメインスレッドさんがいました。
いつものように仕事を任されていたメインスレッドさんでしたが、今回任されたタスクは少々重いなぁと感じていました。
その重さ故に処理速度が大幅に落ちていたのです。そんな時に彼の仕事の一部分を巻き取る役割を果たしてくれるのがWeb Workerです。
Aさんの体は身一つなので一度に一つの仕事しかできなかったのですが、
Web Workerの存在により、Aさんが実行を処理している最中にWeb Workerが別の作業場所で別の処理を行なってくれるようになりました。
このように同時に複数の場所(スレッド)で処理を行うことをマルチスレッドならびに並列処理といったりします。
少し分かりにくいですがこんな感じだと思ってます。。
Web Workerの注意点としては、直接DOMをいじるような操作はできないこと、そしてWeb Workerに関してはスレッド間でメモリを共有しないため、
グローバルに値を変数を使用することはできず、必ずスレッド間で受け渡す必要があるということです。
Web Worker 使ってみる
メインスレッド内でワーカスレッドを生成し、その処理をworker.jsに書くという設定し、
worker.postMessageというメソッドにより値を渡しています。
// メインスレッド
const worker = new Worker('worker.js');
worker.postMessage('Hello World!');
ワーカースレッド内でメインスレッドから送られてきた値を表示する処理を実行します。
// ワーカースレッド
self.addEventListener('message', (message) => {
console.log(message.data);
});
結果が返ってきていると思います。
Hello World!
このWorkerに重たい処理を任せれば処理を高速化できる!?かもしれません。
実はWeb Workerには複数の種類のWorkerが存在します。ここで扱っているのはDedicate Workerという最も基本の形です。
いくつか別の種類のWeb Workerについても紹介していきたいと思います。
Shared Worker 使ってみる
Shared Workerとはその名の通りシェアすることのできるWorkerです。使われるWorkerが共有されているため、Worker内で値を保持することができます。
実際に試していきます。
WorkerからSharedWorkerに接続するために、portを指定してメッセージを送ります。
また、今回はworkerからのメッセージを受け取ります。
portがaddEventListenerを用いるためにはport.start()が必要なので追加しています。
// メインスレッド
const worker = new SharedWorker("worker.js");
worker.port.postMessage("Hello, World");
worker.port.start();
worker.port.addEventListener("message", e => {
console.log(e.data)
});
connectイベントにより接続し、e.ports[0]に格納されている送り手のポートに対しメッセージを送り返します。
let connections = 0;
// ワーカースレッド
self.addEventListener("connect", e => {
const port = e.ports[0];
connections++;
port.addEventListener("message", e => {
port.postMessage(connections);
});
port.start();
});
結果が返ってきます。
1
当然最初は1が返ってくるのですが、
このスクリプトを読み込ませたタブを追加すると、数字は追加した数だけ増えていきます。
このようにworker内で共有された値を受け取ることができます。
これによってオンラインショッピングでの注文重複やメールの二重送信などを防ぐことができたりするんですね〜。
Service Worker 使ってみる
Service WorkerはPWA(Progressive Web Apps)を実現するWeb Apiです。
この実装により、ネイティブアプリのようなユーザー体験を提供することができます。
具体的には、
- オフラインでの動作
- プッシュ通知
- バックグラウンド同期
などが挙げられます。
今回workboxというサービスワーカーを簡単に作成することのできるライブラリを使ってオフラインでも動くページを作成してみました。
workboxはwebpackを介して使用することができます。それではやっていきます。
workboxをインストールします。
npm install --save-dev workbox-sw
npm install --save-dev workbox-webpack-plugin
サービスワーカーを読み込む構文です。
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js');
});
}
webpack.config.js
にworkbox関連も含めた設定を書いていきます。
今回は、キャッシュを1時間保持し、オフラインでも動くように最低限の設定をしました。
const path = require('path');
const workboxPlugin = require('workbox-webpack-plugin');
const cacheId = 'service-worker-sample'
module.exports = {
mode: 'production',
entry: './public/service-worker/src/index.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'public/service-worker')
},
plugins: [
// workboxの設定
new workboxPlugin.GenerateSW({
cacheId: cacheId,
runtimeCaching: [
{
urlPattern: 'URL',
handler: "NetworkFirst",
options: {
cacheName: cacheId + "-html-cache",
expiration: {
maxAgeSeconds: 60 * 60,
}
}
},
],
swDest: __dirname + '/public/service-worker/sw.js'
})
],
};
webpack.config.js
がある階層にてwebpackを実行すると指定場所にsw.jsが生成されます。
同時に生成されたbundle.jsをHTMLに読み込ませるとサービスワーカーが機能し、オフラインでも動くようになります。
おわりに
今回紹介したWebWorkerを簡単にまとめています。
よかったらご覧ください!
Web Workerの動き方
ソースコード
また、間違い等ある場合はご教示いただけると幸いです。