SkyVisualEditor(SVE)で使用されている言語の1つにJavaScriptという言語があります。JavaScriptとは主にWebサイトを構築する際に使用する言語で、様々な用途で使われています。
SVE内の処理でも使われていますし、SVEの機能を拡張したい際にも用いることができます。当ブログのエントリーにもJavaScriptとCSSを用いたSVEの機能拡張についてが記述されています。
SVEの画面のレイアウト変更などに興味がありましたら併せてご覧下さい。
https://base.terrasky.co.jp/articles/cUdoa
今回のブログの内容は、そんなJavaScriptについて少し掘り下げた内容になります。SVEから少し外れた話題ですが、JavaScriptのsetTimeoutなどを用いた従来の並列処理とWeb Workersを用いた並列処理の違いについて学習した内容をまとめてみたいと思います。
Web WorkersはHTML5のAPIです。Web Workersを用いるとJavaScriptでマルチスレッドの並列処理を行うことができます。JavaScriptは基本的にシングルスレッドの言語です。setTimeoutやsetIntervalなどを用いることで、Web Workersを使わなくても擬似的に並列処理はできます。しかし実際はイベントキューにイベントを登録して、一つのスレッド上で順番に処理をしているに過ぎず、結局のところ一度に処理できるスレッドは一つです(図1)。
図1. setTimeoutを用いた並列処理
これに対しWeb Workersはnew Worker(xxx.js)と記述することで、スレッドを新しく作成し引数としてしていたjsファイル(この場合xxx.js)の処理を行うことができます。結果スレッドの数だけ同時に処理を行うことができます(図2)
図2. Web Workersを用いた並列処理
この二つの差をもう少し深く掘り下げてみます。何かJavaScriptで重い処理をしたいとします。例えば5,000,000までの素数の数を数えるというような処理です。setTimeoutを用いたシングルスレッドの処理で、この問題を解決するために、以下のようなソースを書いてみました。
ソース1 setTimeoutを用いた並列処理
var i,j,k; var countNum = 0; var tempNum = 3; // 処理を分割して実行 function singleStress(start){ setTimeout(function(){ if(start < 5000000){ for(i=start;i<=(start+500000);i++){ k=0; for(j=3;j<=Math.sqrt(i);j++){ if(i%j===0){ k=1; break; } } if(k===0){ countNum++; } } singleStress(start + 500000); } // 最後の分割処理 else{ k=0; for(i=start;i<=5000000;i++){ for(j=3;j<=Math.sqrt(i);j++){ if(i%j===0){ k=1; break; } } if(k===0){ countNum++; } } console.log(countNum); } },10); } singleStress(0);
簡単にsetTimeoutを使った並列処理について説明します。setTimeoutは引数2(上のソースでは10)のミリ秒後、引数1の処理を行います。例えば処理1つに3ミリ秒時間がかかるとすると、次の処理が始まるまでの7ミリ秒の間エンドユーザは自由に操作することができます。結果エンドユーザの操作と素数の計算がまるで並列処理されているようになります。
これに対してWeb Workersを用いると以下のような処理になります。
ソース2-a Web Workersを使った並列処理(メインスレッド:main.js)
// Workerオブジェクトを作成する var worker = new Worker('js/calculate.js'); // Workerオブジェクトの処理を受け取る worker.addEventListener('message', function(e) { console.log(e.data.testData); }, false);
ソース2-b Web Workersを使った並列処理(サブスレッド:calculate.js)
// WorkerGlobalScopeオブジェクトを取得する var global_scope = this.self; var i,j,k; var countNum = 0; for(i=3;i<=5000000;i++){ k=0; for(j=3;j<=Math.sqrt(i);j++){ if(i%j===0){ k=1; break; } } if(k===0){ countNum++; } } // 処理した結果をメインスレッドに返す global_scope.postMessage({ testData:countNum });
簡単にWeb Workersを使ったソースについて説明します。Web Workersを使う場合メインスレッドとサブスレッドを作成することになります。サブスレッド用のjsファイルを作成してやり、Workerオブジェクトのコンストラクタに引数としてpathを渡します。(作成された時点でサブスレッドの処理は開始されます。)サブスレッドではWorkerGlobalScopeというスレッド間のデータのやりとりをするためのオブジェクトを作成します。処理が終了したら、WorkerGlobalScopeオブジェクトのpostMessageのメソッドを用いて、メインスレッドに処理したデータを返します。処理自体が全く別のスレッドで行われているため、処理がどれほど重い処理がサブスレッドで行われてもメインスレッドに影響はありません。サブスレッドの処理が行われている間、エンドユーザは自由に処理を行うことができます。
上記の内容に関してだけ言えば、どちらのケースでもエンドユーザの操作を妨害はしません。しかしsetTimeoutの並列処理では、エンドユーザと交換交換で処理をするため処理に時間がかかってしまいます。一方Web Workersを使った並列処理では、サブスレッド上でDOMやWindowオブジェクトなどにアクセスができないという制限があります。
以上のような特徴を持つWeb Workersですが、制限が多く使いどころが難しいというのが正直な感想です。処理の重い3Dのレンダリングか何かやらせようにも、DOMにアクセスができないのではどうしようもありません。(そもそも3DのレンダリングならWebGLを扱えという話ですが...)しかしながらJavaScriptの欠点の一つである処理速度の遅さを解決できる強力な手段の一つであることは確かです。より詳細な仕様を学習し、実案件に導入していけるように努力していきたいと思います。