JavaScript でマルチスレッド・デザインパターン
JavaScriptでデザインパターンを書いてみる。とはいえ、いまさらFactoryパターンなど書いても面白くないので、マルチスレッド・デザインパターンにする。
もちろん、JavaScriptの言語仕様にスレッドなどない。しかし、ブラウザ環境では実質的にスレッドが存在する。スレッドが発生する場面には2種類ある。1つは、イベントである。例えば、ユーザのクリック動作によってonclickイベントが発生した時、onclickイベントハンドラだけを処理するスレッドが発生するとみなせる。もう1つは、setTimeoutとsetIntervalメソッドである。これらのメソッドでは、一定時間後に、別の処理を実行させるが、この処理は現在の処理と並行的に実行される。つまり、スレッドとみなせる。個人的には、setTimeoutをアニメーションなどの小細工だけに使うのはもったいないと思う。setTimeoutの本領は、並行処理の利用によるレスポンス向上にある。JavaScriptでリッチ・クライアントを実現するには必要不可欠である。
今回は、Worker Threadパターンを書いてみた。とはいえ、ブラウザ環境のスレッドモデルはかなり貧弱である。当然、waitやnotifyもないし、そもそもsleepすらない。従って、無理もかなりあるが、容赦されたい。
var Thread = function(){}; Thread.prototype = { start: function(){ setTimeout(this.run.bind(this), 10); } } var Client = Class.create(); Client.prototype = (new Thread).extend({ initialize: function(name, channel){ this.channel = channel; this.name = name; this.count = 0; this.maxCount = 5; }, run: function(){ var request = new Request(this.name, this.count); if(this.channel.putRequest(this, request)){ this.count += 1; if(this.count != this.maxCount){ this.start(); } } } }) var Worker = Class.create(); Worker.prototype = (new Thread).extend({ initialize: function(name, channel){ this.name = name; this.channel = channel; }, run: function(){ var request = this.channel.takeRequest(this); if(request){ request.execute(this); this.start(); } } }) var Channel = Class.create(); Channel.prototype = { initialize: function(maxRequest, threads){ this.requestQueue = new Array(maxRequest); this.head = this.tail = this.count = 0; this.waitSet = [] this.threadPool = new Array(threads); for(var i = 0; i < threads; i++){ this.threadPool[i] = new Worker("Worker-" + i, this); } }, startWorkers: function(){ for(var i = 0; i < this.threadPool.length; i++){ this.threadPool[i].start(); } }, wait: function(thread){ this.waitSet.push(thread); }, notifyAll: function(){ var set = this.waitSet; this.waitSet = []; for(var i = 0; i < set.length; i++){ set[i].start(); } }, putRequest: function(thread, request){ if(this.count >= this.requestQueue.length){ this.wait(thread); return null; } this.requestQueue[this.tail] = request; this.tail = (this.tail + 1) % this.requestQueue.length; this.count++; this.notifyAll(); return true; }, takeRequest: function(thread){ if(this.count <= 0){ this.wait(thread); return null; } var request = this.requestQueue[this.head]; this.head = (this.head + 1) % this.requestQueue.length; this.count--; this.notifyAll(); return request; } }
例によって、prototype,jsを使っている。
ClientとWorkerはThreadのサブクラスである。Threadクラスでは、startメソッドによって、runメソッドを実行するスレッドを発生させる。
そして、重い処理を実行するRequestクラスと、Mainクラス、それにログを記録するLoggerクラスを追加する。
var Request = Class.create(); Request.prototype = { initialize: function(name, number){ this.name = name; this.number = number; }, execute: function(thread){ for(var i = 0; i< 100000; i++){ Math.log(i); } logger.puts(thread.name + " execute " + this.name + " " + this.number); } } var Main = Class.create(); Main.prototype = { initialize: function(){ var channel = new Channel(5, 2); channel.startWorkers(); ( new Client("Alice", channel) ).start(); ( new Client("Bobby", channel) ).start(); ( new Client("Chris", channel) ).start(); } } var Logger = Class.create(); Logger.prototype = { initialize: function(element){ this.log = ""; this.element = $(element); }, puts: function(str){ this.log += str + "<br />"; this.element.innerHTML = this.log; } } var logger = new Logger("log"); var main = new Main;
これらのコードをworker.jsに記述して、
<html> <body> <div id = "log"> </div> </body> <script language="JavaScript" src="js/prototype.js"></script> <script language="JavaScript" src="js/worker.js"></script> </html>
というhtmlファイルを開くと、
Worker-0 execute Alice 0 Worker-1 execute Bobby 0 Worker-0 execute Chris 0 Worker-1 execute Alice 1 Worker-0 execute Bobby 1 Worker-1 execute Chris 1 Worker-0 execute Alice 2 Worker-1 execute Bobby 2 Worker-0 execute Chris 2 Worker-1 execute Alice 3 Worker-0 execute Bobby 3 Worker-1 execute Alice 4 Worker-0 execute Bobby 4 Worker-1 execute Chris 3 Worker-0 execute Chris 4
という実行結果が得られる。このとき、1行ずづ処理結果が表示される。これは、Workerで、1つのRequestの処理が終了したならば、次のRequestを発生されるスレッドを発生させている為である。こうしないと、一度に複数の処理結果が表示され、応答速度が低下してしまう。
Worker Threadパターンを用いる主な利点は次の3点である。
- 処理の要求者(Client)・内容(Request)・実行者(Worker)を分離できる。
- 同時に実行する処理数の上限を制限できる。
- 1つのRequestが終了しだい、結果をすぐに反映できる。
参考書籍
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2002/06
- メディア: 単行本
- クリック: 34回
- この商品を含むブログ (46件) を見る