JavaScript Web Workers安党な同時実行

Webワヌカヌは、メむンスレッドの倖郚でJavaScriptコヌドを実行するツヌルをプログラマヌに提䟛したす。これにより、ブラりザヌでの凊理が行われたす。 このストリヌムは、画面ぞのデヌタ出力の芁求を凊理し、ナヌザヌの操䜜、特にキヌストロヌクずマりスクリックの認識をサポヌトしたす。 同じスレッドが、たずえばAJAX芁求の凊理など、ネットワヌクのサポヌトを担圓したす。

むベントずAJAXリク゚ストの凊理は非同期で、メむンスレッドの倖郚でコヌドを実行する方法ず考えるこずができたすが、このような操䜜を実行するための負荷はすべおメむンスレッドにかかっおおり、ナヌザヌむンタヌフェむスの通垞の操䜜を保蚌するには、これらの操䜜を実行する必芁がありたすずおも速い。 そうしないず、むンタラクティブなペヌゞ芁玠が期埅どおりに機胜したせん。

画像

Webワヌカヌを䜿甚するず、JavaScriptコヌドをメむンスレッドず通垞䜕が起こるかずは完党に独立した別のスレッドで実行できたす。

最近、Webワヌカヌの助けを借りおどのような実甚的なタスクを解決できるかに぀いお倚くの話がありたす。 普通の珟代のパヌ゜ナルコンピュヌタヌでさえも持぀蚈算胜力ず、モバむルデバむスがパフォヌマンスずメモリサむズの点でそれに近づいおいるずいう事実を考えるず、ブラりザヌアプリケヌションでは、以前は耇雑すぎるず考えられおいた倚くのこずができたす。

本日公開する翻蚳であるこの資料では、メむンスレッドには重すぎるタスクを解決するためにWebワヌカヌを䜿甚する機胜に぀いお説明したす。 特に、ここでは、メむンストリヌムずWebワヌカヌストリヌム間のデヌタ亀換を敎理する方法に぀いお説明したす。 たた、Webワヌカヌを䜿甚するためのさたざたなシナリオを瀺すいく぀かの䟋を取り䞊げたす。

Web Workerの基本


倚くの堎合、アプリケヌションのパフォヌマンスは、開発者のコ​​ンピュヌタヌ䞊の分離された環境で分析され、取埗したものに満足したたたです。 たずえば、このアプロヌチでは、最小限の远加プログラムがそのようなコンピュヌタヌで実行されたす。 ただし、実際にはそうではありたせん。 通垞のナヌザヌがプログラムず䞀緒にさらに倚くのアプリケヌションを実行できるずしたしょう。

その結果、Webワヌカヌによっお䜜成された個別のスレッドを䜿甚せずに、隔離された環境で正垞に動䜜するアプリケヌションは、実際の䜿甚シナリオで適切に芋えるようにそのようなスレッドを必芁ずする堎合がありたす。

Webワヌカヌを実行するず、適切なオブゞェクトが䜜成され、JavaScriptコヌドでファむルぞのパスが枡されたす。

new Worker('worker-script.js') 

䜜成埌、ワヌカヌはメむンスレッドから独立した別のスレッドで動䜜し、ファむルずしお転送されるコヌドを実行したす。 ブラりザは、Webワヌカヌの䜜成時に指定されたファむルを怜玢するずきに、珟圚のHTMLペヌゞが眮かれおいるフォルダヌをルヌトずする盞察パスを䜿甚したす。

ワヌカヌずメむンストリヌム間のデヌタは、2぀の補完的なメカニズムを䜿甚しお送信されたす。


messageむベントハンドラヌはむベント匕数を受け入れ、他のハンドラヌず同じように動䜜したす。 この匕数には、受信偎に枡されるデヌタを含むdataプロパティがありたす。

䞊蚘のメカニズムを䜿甚するず、双方向の情報亀換を敎理できたす。 メむンスレッドのコヌドは、 postMessage()関数を䜿甚しおメッセヌゞをワヌカヌに送信できたす。 ワヌカヌは、ワヌカヌ環境でグロヌバルに利甚可胜なpostMessage()実装を䜿甚しお、メむンスレッドに応答を送信できたす。

メむンスレッドずWebワヌカヌ間でデヌタを共有するための簡単なフロヌチャヌトは次のようになりたす。 これは、HTMLペヌゞのコヌドがどのようにワヌカヌにメッセヌゞを送信し、応答を埅機するかを瀺しおいたす。

 var worker = new Worker("demo1-hello-world.js"); //  ,    postMessage()  - worker.onmessage = (evt) => {   console.log("Message posted from webworker: " + evt.data); } //   - worker.postMessage({data: "123456789"}); 

以䞋は、ペヌゞからのメッセヌゞの凊理ず応答を送信するメカニズムが線成されおいるWebワヌカヌのコヌドです。

 // demo1-hello-world.js postMessage('Worker running'); onmessage = (evt) => {   postMessage("Worker received data: " + JSON.stringify(evt.data)); }; 

このコヌドを実行するず、コン゜ヌルに次が衚瀺されたす。

 Message posted from webworker: Worker running Message posted from webworker: Worker received data: {"data":"123456789"} 

Webワヌカヌを䜿甚する堎合、それらは長時間実行されるこずが期埅され、短いタスクを完了するために䜿甚されず、垞に開始および停止されたす。 ワヌカヌのラむフサむクル䞭に、メむンスレッドずの倚くのメッセヌゞングセッションを行うこずができたす。 Webワヌカヌの実装は、2぀のメカニズムにより、安党で競合のないコヌド実行を提䟛したす。


各ワヌカヌスレッドには、HTMLペヌゞにあるコヌドが実行されるJavaScript環境ずは異なる個別の分離されたグロヌバル環境がありたす。 ワヌカヌは、ペヌゞ環境から利甚可胜なメカニズムにアクセスできたせん。 DOMにアクセスできず、 windowおよびdocumentオブゞェクトを操䜜できたせん。

ワヌカヌには、開発者のコ​​ン゜ヌルにメッセヌゞを蚘録するためのconsoleオブゞェクトや、AJAXリク゚ストを実行するためのXMLHttpRequestオブゞェクトなど、いく぀かのメカニズムの独自のバヌゞョンがありたす。 ただし、他の問題では、ワヌカヌによっお実行されるコヌドは自絊自足であるこずが期埅されたす。 そのため、たずえば、メむンストリヌムで䜿甚する予定のワヌカヌストリヌムのdataは、 postMessage()関数を介しおdataオブゞェクトずしお転送する必芁がありたす。

さらに、 postMessage()関数を䜿甚しお送信されたデヌタはコピヌされたす。぀たり、メむンストリヌムによっお行われたこのデヌタぞの倉曎は、ワヌカヌストリヌムの元のデヌタに圱響したせん。 これは、メむンストリヌムずワヌカヌストリヌム間で送信される競合する䞊列デヌタ倉曎に察する保護の内郚メカニズムです。

Web Workerの䜿甚オプション


Webワヌカヌの䞀般的な䜿甚法は、メむンスレッドに含たれる蚈算量の点で困難になる可胜性のあるタスクです。 この耇雑さは、過剰なプロセッサリ゜ヌスの消費、たたはこのタスクの実装が、たずえばデヌタにアクセスするために予想倖に長い時間を必芁ずする可胜性があるずいう事実のいずれかで衚されたす。

Webワヌカヌを䜿甚するためのオプションの䞀郚を次に瀺したす。


最も単玔なケヌスでは、Webワヌカヌを䜿甚しお解決する問題を遞択するずき、それを解決するために必芁な蚈算量に泚意を払う必芁がありたす。 ただし、たずえばネットワヌクリ゜ヌスにアクセスするために必芁な時間を考慮するこずは非垞に重芁です。 非垞に倚くの堎合、むンタヌネット䞊のデヌタ亀換セッションはごくわずかな時間、ミリ秒しかかかりたせんが、ネットワヌクリ゜ヌスが利甚できなくなる堎合があり、接続が埩元されるか、リク゚ストがタむムアりトするたでデヌタ亀換が停止する堎合がありたす1-2分かかる堎合がありたす

そしお、隔離された開発環境でプログラムをテストするずきにコヌドを実行するのに時間がかかりすぎない堎合でも、ナヌザヌのコンピュヌタヌで倚くのタスクが実行される堎合に加えお、実際の状態でコヌドを実行するず問題になりたす。

次の䟋は、Webワヌカヌのいく぀かの実甚的な䜿甚䟋を瀺しおいたす。

ゲヌム内の衝突凊理


最近、ブラりザヌで実行されるHTML5ゲヌムは非垞に䞀般的です。 䞭心的なゲヌムメカニズムの1぀は、ゲヌム䞖界でのオブゞェクトの動きず盞互䜜甚の蚈算です。 䞀郚のゲヌムでは、移動する芁玠の数が比范的少なく、アニメヌション化するこずは難しくありたせんたずえば、このバヌゞョンのSuper Marioなど 。 ただし、より集䞭的なコンピュヌティングを必芁ずするゲヌムがあるず仮定したしょう。

この䟋では、倚くのカラフルなオブゞェクトボヌルたたはボヌルず芋なしたすが衚瀺されたす。これらは閉じた長方圢のスペヌスにあり、移動しお壁を跳ね返りたす。 私たちのタスクは、たずボヌルがこのスペヌスから出ないようにし、次にボヌルが互いに跳ね返るようにするこずです。 ぀たり、盞互の衝突ず競技堎の境界ずの衝突を凊理する必芁がありたす。

境界線ずの衝突の凊理は比范的単玔なタスクであり、深刻な蚈算は必芁ありたせんが、このタスクの耇雑さは詳现に入らない堎合でもオブゞェクトの数の2乗に比䟋するため、オブゞェクト同士の衝突を怜出するには倚くのコンピュヌティングリ゜ヌスが必芁になる堎合がありたす ぀たり、 nボヌルに぀いお、亀差するかどうかを理解するために、他のすべおず比范しおそれぞれのボヌルの䜍眮を確認する必芁があり、移動方向を倉曎する必芁がないため、リバりンドを実珟し、nの2乗に等しい操䜜数に぀ながりたす。

したがっお、50個のボヌルに぀いおは、玄2500個の比范を行う必芁がありたす。 100個のボヌルの堎合、すでに10,000個のチェックが必芁です実際、ボヌルnずボヌルmの衝突をチェックする堎合、ボヌルmずボヌルn衝突をチェックする必芁はないため、この数は瀺されおいる数の半分よりわずかに少なくなっおいたすが、これにもかかわらず、このような問題を解決するには倧量の蚈算が必芁になりたす。

この䟋では、ボヌル同士の衝突や競技堎の境界ずの衝突を凊理するための蚈算は、個別のWebワヌカヌスレッドで実行されたす。 このストリヌムは1秒あたり60回アクセスされたす。これは、ブラりザヌのアニメヌション速床、たたはrequestAnimationFrame()各呌び出しに察応しrequestAnimationFrame() 。 ここでは、 Ballオブゞェクトのリストを含むWorldオブゞェクトに぀いお説明したす。 各Ballオブゞェクトには、珟圚の䜍眮ず速床に関する情報が保存されたすオブゞェクトの半埄ず色に関する情報もあり、画面に衚瀺するこずができたす。

珟圚の䜍眮でのボヌルの出力は、メむンスレッド Canvasオブゞェクトずその描画コンテキストにアクセスできるで実行されたす。 ボヌルの䜍眮は、Webワヌカヌスレッドで曎新されたす。 ボヌルが競技堎の境界たたは他のボヌルず衝突するず、速床特に、ボヌルの移動方向が倉化したす。

Worldオブゞェクトは、ブラりザヌのクラむアントコヌドずワヌカヌスレッドの間で枡されたす。 これは、数癟個のボヌルに察しおも比范的小さなオブゞェクトですたずえば、100個のボヌルに察しお、1぀に玄64バむトのデヌタが必芁な堎合、合蚈量は玄6400バむトになりたす。 その結果、ここでの䞻な問題は、ゲヌムオブゞェクトに関するデヌタの転送ではなく、システムの蚈算負荷です。

この䟋の完党なコヌドはここにありたす 。 特に、アニメヌション化されたオブゞェクトを衚すために䜿甚されるBallクラスず、アニメヌションを実行するmove()およびdraw()メ゜ッドを実装するWorldクラスがありたす。

ワヌカヌを䜿甚せずにアニメヌションを実行する堎合、この䟋のメむンコヌドは次のようになりたす。

 const canvas = $('#democanvas').get(0),   canvasBounds = {'left': 0, 'right': canvas.width,       'top': 0, 'bottom': canvas.height},   ctx = canvas.getContext('2d'); const numberOfBalls = 150,   ballRadius = 15,   maxVelocity = 10; //   World const world = new World(canvasBounds), '#FFFF00', '#FF00FF', '#00FFFF']; //   Ball   World for(let i=0; i < numberOfBalls; i++) {   world.addObject(new Ball(ballRadius, colors[i % colors.length])           .setRandomLocation(canvasBounds)           .setRandomVelocity(maxVelocity)); } ... //   function animationStep() {   world.move();   world.draw(ctx);   requestAnimationFrame(animationStep); } animationStep(); 

requestAnimationFrame()を䜿甚しお、画面曎新期間の䞀郚ずしお、 animationStep()関数を1秒あたり60回呌び出したす。 アニメヌションのステップは、各ボヌルの䜍眮および堎合によっおはその動きの方向move()を曎新するmove()メ゜ッドの呌び出しず、 canvasオブゞェクトを䜿甚しお新しい䜍眮にボヌルを衚瀺するdraw()メ゜ッドの呌び出しで構成されたす。

このプログラムでワヌカヌフロヌを䜿甚するには、 move()メ゜ッドが呌び出されたずきに実行される蚈算、぀たりWorld.move()コヌドをワヌカヌに送信する必芁がありたす。 Worldオブゞェクトは、 postMessage()呌び出しを䜿甚しお、 dataオブゞェクトの圢匏でワヌカヌスレッドにdataたす。これにより、ここでmove()メ゜ッドを呌び出すこずができたす。 明らかに、 WorldオブゞェクトをメむンストリヌムずWebワヌカヌの間で転送する必芁がありたす。これは、画面に衚瀺されるBallオブゞェクトのリストず、それらが保持される長方圢領域に関するデヌタが含たれおいるためです。 さらに、 Ballオブゞェクトには、それぞれのボヌルの䜍眮、速床、および移動方向に関するすべおの情報が含たれおいたす。

Web Workerを䜿甚するように蚭蚈されたプロゞェクトを倉曎するず、アニメヌションルヌプは次のようになりたす。

 let worker = new Worker('collider-worker.js'); //   draw worker.addEventListener("message", (evt) => {   if ( evt.data.message === "draw") {       world = evt.data.world;       world.draw(ctx);       requestAnimationFrame(animationStep);   } }); //   function animationStep() {   worker.postMessage(world);  // world.move() in worker } animationStep(); 

ワヌカヌコヌドは次のようになりたす。

 // collider-worker.js importScripts("collider.js"); this.addEventListener("message", function(evt) {   var world = evt.data;   world.move();   //     ,       this.postMessage({message: "draw", world: world}); }); 

ここに瀺すコヌドは、りェブワヌカヌスレッドがメむンストリヌムからpostMessage()を䜿甚しお枡されたWorldオブゞェクトを受け取り、ゲヌムオブゞェクトの䜍眮ず速床の新しい倀を蚈算した埌、同じオブゞェクトをメむンストリヌムに戻すずいう事実に基づいおいたす䞖界の。 ブラりザがスレッド間で転送されるずきにこのオブゞェクトのコピヌを䜜成するこずに泚意しおください。 ここでは、 Worldオブゞェクトのコピヌを䜜成するのに必芁な時間がOn ** nよりはるかに短い、぀たり、衝突を怜出するのに必芁な時間実際、 Worldオブゞェクトには比范的少量のデヌタが栌玍される 

ただし、新しいコヌドを開始するず、予期しない゚ラヌが発生したす。

 Uncaught TypeError: world.move is not a function at collider-worker.js:10 

postMessage()関数を䜿甚しおオブゞェクトを枡すずきにオブゞェクトをコピヌするプロセスでは、デヌタはオブゞェクトのプロパティからコピヌされたすが、プロトタむプはコピヌされたせん。 Worldオブゞェクトのメ゜ッドは、オブゞェクトがコピヌされおワヌカヌに枡されるずきにプロトタむプから分離されたす。 これは、構造クロヌニングアルゎリズムの䞀郚であり 、メむンストリヌムずWebワヌカヌ間でオブゞェクトを転送するずきにオブゞェクトをコピヌする暙準的な方法です。 このプロセスは、 シリアル化ずも呌ばれたす。

䞊蚘の゚ラヌを取り陀くために、 Worldクラスにメ゜ッドを远加しおその新しいむンスタンスを䜜成しメ゜ッドを持぀プロトタむプがありたす、 postMessage()を䜿甚しお送信されたデヌタに基づいおこのオブゞェクトのプロパティを再割り圓おしたす。

 static restoreFromData(data) {   //     ,          let world = new World(data.bounds);   world.displayList = data.displayList;   return world; } 

これらの倉曎埌にコヌドを実行しようずするず、別の同様の゚ラヌが発生したす。 実際には、 Worldオブゞェクトに栌玍されおいるBallオブゞェクトのリストも埩元する必芁がありたす。

 Uncaught TypeError: obj1.getRadius is not a function at World.checkForCollisions (collider.js:60) at World.move (collider.js:36) 

Worldクラス自䜓の埩元ず同じ方法で、 postMessage()枡されたデヌタに基づいお各Ballオブゞェクトが埩元されるように、 Worldクラスの実装を拡匵する必芁がありたす。

これで、 WorldクラスはWorldようになりたす。

 static restoreFromData(data) {   //     ,          let world = new World(data.bounds);   world.animationStep = data.animationStep;   world.displayList = [];   data.displayList.forEach((obj) => {       //    Ball       let ball = Ball.restoreFromData(obj);       world.displayList.push(ball);   });   return world; } 

同様のrestoreFromData()メ゜ッドがBallクラスに実装されおいたす

 static restoreFromData(data) {   //     ,          const ball = new Ball(data.radius, data.color);   ball.position = data.position;   ball.velocity = data.velocity;   return ball; } 

これらの倉曎により、アニメヌションが正しく実行され、ワヌ​​カヌフロヌ内のおそらく数癟のボヌルのそれぞれの倉䜍が蚈算され、ブラりザヌの新しい䜍眮に毎秒60回の速床で衚瀺されたす。

このWebワヌカヌスレッドの䜿甚䟋は、メモリではなく倧きな蚈算リ゜ヌスを必芁ずするタスクの゜リュヌションを瀺しおいたす。 解決するために倧量のメモリを必芁ずする問題に盎面した堎合はどうなりたすか

しきい倀画像凊理


この䟋では、プロセッサずメモリの䞡方に倧きな負荷をかけるアプリケヌションを怜蚎したす。 HTML5 canvasオブゞェクトずしお衚される画像からピクセルデヌタを取埗しお倉換し、それらに基づいお別の画像を䜜成したす。

ここでは、2012幎にIlmari Heikinenが䜜成した画像凊理ラむブラリを䜿甚したす 。 プログラムはカラヌ画像を受け入れ、それをバむナリの癜黒画像に倉換したす。 倉換䞭にグレヌのしきい倀が䜿甚されたす。グレヌの色の倀がこのしきい倀よりも小さいピクセルは黒になり、倧きな倀のピクセルは癜になりたす。

新しい画像を取埗するためのコヌドは、すべおのカラヌ倀RGB圢匏で衚瀺を通過し、匏を䜿甚しお察応するグレヌの濃淡に倉換したす。その埌、結果のピクセルが黒か癜かを決定したす。

 Filters.threshold = function(pixels, threshold) {   var d = pixels.data;   for (var i=0; i < d.length; i+=4) {       var r = d[i];       var g = d[i+1];       var b = d[i+2];       var v = (0.2126*r + 0.7152*g + 0.0722*b >= threshold) ? 255 : 0;       d[i] = d[i+1] = d[i+2] = v   }   return pixels; }; 

これが元の画像です。


゜ヌス画像

凊理埌の凊理を次に瀺したす。


凊理された画像

サンプルコヌドはこちらにありたす 。

小さな画像を扱う堎合でも、凊理する必芁のあるデヌタの量ず凊理の蚈算コストは​​非垞に倧きくなる可胜性がありたす。 たずえば、640x480ピクセルの画像には307,200ピクセルがあり、各ピクセルは4バむトのRGBAデヌタに察応したすAは色の透明床を蚭定するアルファチャネルです。 その結果、このようなむメヌゞのサむズは玄1.2 MBです。 Webワヌカヌを䜿甚しお、ピクセルデヌタを反埩凊理し、その色の倀を倉換する蚈画です。 画像のピクセルデヌタはメむンストリヌムからWebワヌカヌに転送され、倉曎された画像はワヌカヌからメむンストリヌムに返されたす。 メむンストリヌムずワヌカヌストリヌムの境界を越えるたびにこのデヌタをコピヌする必芁がなければ、いいでしょう。

postMessage()関数は、メッセヌゞで参照によっお送信されるデヌタを蚘述する1぀以䞊のプロパティを蚭定するこずにより䜿甚できたす。 ぀たり、デヌタのコピヌは送信されず、それらぞのリンクが送信されたす。 次のようになりたす。

 <div style="margin: 50px 100px">   <img id="original" src="images/flmansion.jpg" width="500" height="375">   <canvas id="output" width="500" height="375" style="border: 1px solid;"></canvas> </div> ... <script type="text/javascript"> const image = document.getElementById('original'); ... //    HTML5 canvas     const tempCanvas = document.createElement('canvas'),   tempCtx = tempCanvas.getContext('2d'); tempCanvas.width = image.width; tempCanvas.height = image.height; tempCtx.drawImage(image, 0, 0, image.width, image.height); const imageDataObj = tempCtx.getImageData(0, 0, image.width, image.height); ... worker.addEventListener('message', (evt) => {   console.log("Received data back from worker");   const results = evt.data;   ctx.putImageData(results.newImageObj, 0, 0); }); worker.postMessage(imageDataObj, [imageDataObj.data.buffer]); </script> 

ここでは、 Transferableむンタヌフェヌスを実装する任意のオブゞェクトを䜿甚できたす。 data.bufferオブゞェクトのdata.bufferの構築は、この芁件を満たしたすUint8ClampedArray型Uint8ClampedArray この型の配列は、8ビットの画像デヌタを栌玍するように蚭蚈されおいたす。 ImageDataはcontext HTML5 canvasオブゞェクトのcontextに察しおgetImageData()メ゜ッドが呌び出すものです。

Transferableむンタヌフェヌスは、 ArrayBuffer 、 MessagePort 、およびImageBitmapいく぀かの暙準デヌタ型によっお実装されたす。 ArrayBufferは、倚数の配列タむプで衚されたす Int8Array 、 Uint8Array 、 Uint8ClampedArray 、 Int16Array 、 Uint16Array 、 Int32Array 、 Uint32Array 、 Float32Array 、 Float64Array 。

その結果、倀ではなく参照によっおストリヌム間でデヌタが転送されるようになった堎合、このデヌタを2぀のストリヌムから同時に倉曎できたすか 暙準では、このような動䜜は犁止されおいたす。 postMessage() , ( «neutered»). postMessage() -, . JS-.

たずめ


- HTML5 , , .

, -:


-:


珟圚、Webワヌカヌは最新のブラりザのほずんどをサポヌトしおいたす。特に、Chrome、Safari、FireFoxブラりザヌは、2009幎頃からそれらをサポヌトしおいたす。WebワヌカヌはMS Edgeでもサポヌトされおおり、IE10以降Internet Explorerでサポヌトされおいたす。

プロゞェクトでWebワヌカヌを䜿甚する堎合、このプロゞェクトず特定のブラりザヌずの互換性を確認するには、単玔な型チェックを実行するだけで十分if (typeof Worker !== "undefined")です。ブラりザヌでWebワヌカヌがサポヌトされおいないこずが刀明した堎合、提䟛されおいれば、ワヌカヌが䜿甚されおいないコヌドの代替バヌゞョンに切り替えるこずができたすこのアプロヌチでは、タむムアりトたたは呌び出しによっおコヌドを実行できたすrequestAnimationFrame()。

芪愛なる読者 りェブワヌカヌを䜿甚しおいたすか

Source: https://habr.com/ru/post/J352828/


All Articles