nsIThreadPoolの使い方

今回も、Firefox拡張機能開発のお話。


Firefox3からThread関係のインターフェースが追加されています。
ネットワークの通信処理をかいているのですが、nsIAsyncInputStreamやnsIAsyncOPenStreamの、AsyncWait()の使い方がわからないので、ベーシックな感じでThreadで処理しています。
# AsyncWait()を使う場合, nsIBinaryInputStreamって使っていいのかな。


基本的なThreadの処理はMDCにあるサンプルコードを見ればわかります。
しかし、処理ごとにThreadを作っていると、Thread管理するためのコードが必要になったり、Thread数が爆発してしまったりします。


さて、XULPlanetのXPCOMの項目にThread関係があります。

nsIThread
nsIThreadEventFilter
nsIThreadInternal
nsIThreadJSContextStack
nsIThreadManager
nsIThreadObserver
nsIThreadPool

http://www.xulplanet.com/references/xpcomref/group_XPCOM.html

ここを見ると、nsIThreadPoolというインターフェースがあるのがわかると思います。
ThreadPoolでは、あらかじめいくつかのThreadを用意しておき、処理が割り当てられると用意しているThreadをその処理に割り当てます。
また、一定以上の処理の割り当てによって、用意しているThreadが足りなくなると、処理中のThreadがあくまで処理を待機します。
この機能によって、Threadを使った処理のキューイングのようなことができ、また、簡単に並列に実行する処理数を変更することもできます。
nsIServerSocketを使うときとか、かなり便利です。(てか、それを使うために調べたんですが)


では、使い方をサンプルコードにて。

// XPCOMUtilsオブジェクトをロード
// XPCOMUtils.generateQI を利用すると、 QueryInterfaceを簡単に記述可能
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

// nsIThreadManagerサービスを取得
var ThreadManager = Components.classes["@mozilla.org/thread-manager;1"].getService();

// thread pool を動作させるためのスレッドを用意
//var thread = ThreadManager.newThread(0);

// thread pool を作成
var threadPool = Components.classes["@mozilla.org/thread-pool;1"].createInstance(Components.interfaces.nsIThreadPool);

// thread で処理したいお仕事
var work = function(id) {
  this.workId = id;
}
work.prototype = {
  run: function() { dump("お仕事" + this.workId + "\n"); },
  QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIRunnable])
}

// thread に thread pool を割り当て
//thread.dispatch(threadPool, thread.DISPATCH_NORMAL);

// お仕事を thread pool に割り当て
threadPool.dispatch(new work(1), threadPool.DISPATCH_NORMAL);
threadPool.dispatch(new work(2), threadPool.DISPATCH_NORMAL);
threadPool.dispatch(new work(3), threadPool.DISPATCH_NORMAL);
threadPool.dispatch(new work(4), threadPool.DISPATCH_NORMAL);

といった感じ。


nsIThreadPoolは nsIRunnable を実装しているので、thread.dispatch() に渡してやることで、threadPool の処理をバックグラウンドで実行を開始します。
訂正: nsIThreadPool用にThreadを用意しなくてもdispatchで処理を割り当てると呼び出しThreadをブロックすることなく実行してくれるみたいです。
あとは、threadPool.dispatch() に、通常のThread処理と同じように nsIRunnableインターフェースを実装したオブジェクトを渡してあげると、threadPoolが割り当てられた処理をさらに別スレッドで動かしてくれます。


thread poolのthread数の調整ですが、

Properties

PRUint32 idleThreadLimit
Get/set the maximum number of idle threads kept alive.
PRUint32 idleThreadTimeout
Get/set the amount of time in milliseconds before an idle thread is destroyed.
PRUint32 threadLimit
Get/set the maximum number of threads allowed at one time in this pool.

http://www.xulplanet.com/references/xpcomref/ifaces/nsIThreadPool.html

と、プロパティがあります。 threadLimit が処理を行う最大スレッド数です。これを増減すれば同時に処理するスレッド数が変わると思います。
が、未確認です。


なぜ未確認か。それは、ThreadPoolを利用する処理を組み込み、ThreadPoolのすべての処理が終了すると、Firefoxがおちるという症状が発生しています。
状況整理もかねて、このエントリを書いています。
原因が上記のコードに含まれている場合はあとで修正します。


追記:
とりあえず、単純な処理でThreadPoolを使う上ではFirefoxがクラッシュするということはありませんでした。


今、書いているコードでクラッシュするのは通信処理との兼ね合いのよう。
nsISocketTransportは、nsITransportを継承していて、nsITransportは nsIStreamTransportService から生成されます。そして、nsIStreamTransportService内部ではnsIThreadPoolを使っています。
nsISocketTransportがどう生成されるのか次第ですが、nsIStreamTransportServiceと競合してるような・・・そんな気が。
もっと詳しく調べてみます・・・.