IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  XML | Web development  >

XUL でのマルチスレッド・プログラミングを探る

Google、Yahoo!、Microsoft の Bing から同時に検索結果を取得する

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

ダウンロード

原文はこちら

原文はこちら


レベル: 中級

Michael Galpin, Software architect, eBay

2009年 9月 01日

XUL を使ってクロスプラットフォームのデスクトップ・アプリケーションを作成すると、JavaScript や CSS、さらには HTML のスキルの強化につながります。XUL のクロスプラットフォーム機能は、どのプラットフォームにも共通の最小の機能の集まりではありません。XUL はデスクトップ・アプリケーションのツールキットに期待するような機能も提供します。その 1 つが、ネイティブ・スレッドへのアクセスです。XUL では、JavaScript から直接ネイティブ・スレッドにアクセスして、並列で実行されるコードを作成することさえ可能です。この記事では XUL のマルチスレッド化機能について調べ、複数のスレッドを使用してデータを取得するアプリケーションを作成します。インターネットで複数のリモート・データ・ソースにアクセスする典型的な I/O バウンドのアプリケーションを例に、XUL の複数のスレッドを使用してアプリケーションの実行に要する時間を短縮します。そしてユーザーがこのアプリケーションを使って、Google、Yahoo!、そして Microsoft® の Bing という 3 つのよく使われている検索エンジンによる検索結果を表示し、比較できるようにします。

前提条件

この記事では XUL (XML User Interface Language) アプリケーションを開発します。XUL による開発について十分な知識があると役に立ちますが、絶対に必要というわけではありません。XUL はもっぱら XML によるウィジェット作成用の言語であり、ある意味 HTML に似ていて、JavaScript および CSS をサポートします。XUL では他の言語を一緒に使用することもできますが、この記事で使用するのは JavaScript だけです。そのため JavaScript の経験は必要ですが、これ以外の言語についての知識は必要ありません。この記事で使用する並行性 API は Mozilla の JavaScript 1.9.1 で導入されたもので、Firefox 3.5 に含まれています。この記事に記載するコードを実行するには、Firefox 3.5 または Mozilla の XULRunner 1.9.1 のいずれかを使用することができます。これらのツールへのリンクについては、「参考文献」を参照してください。




上に戻る


問題の定義

よく使われる頭字語
  • API: Application Program Interface
  • CPU: Central Processing Unit
  • CSS: Cascading StyleSheets
  • HTML: HyperText Markup Language
  • I/O: Input-Output
  • JSON: JavaScript Object Notation
  • UI: User Interface
  • URL: Uniform Resource Locator
  • XML: Extensible Markup Language
  • XPCOM: Cross Platform Component Object Model
  • XUL: XML User Interface Language

Herb Sutter は 2005年に、何の苦労もなくパフォーマンスが向上する時代は終わったと宣言しました。彼の分析と結論は、いずれも極めて単純です。つまり、ムーアの法則は自然な成り行きを辿った結果、もはやプロセッサーが高速化されることによってコンピューターが進化することはなくなったのです。これまでソフトウェア開発者はプロセッサーの高速化を、常に必要な計算量を増やすことによって大いに利用してきました。これは単純な話で、コンピューターの速度が速ければ速いほど、処理できる量は増えるからです。現在コンピューターを高速化する手段となっているのは、コアの追加です。処理する量はさらに増やすことができますが、それには並行プログラミングを使用しなければなりません。並行プログラミングによって、追加されたコアを使用しなければならないのです。

おそらくこの話は耳にしたことがあると思いますが、多くのプログラミング言語とプラットフォームでは、開発者がマルチコア・コンピューターを利用できるように新しい並行性機能を追加するようになっています。このことによって、マルチスレッド・プログラミング API が既に存在する言語では、より優れた API の作成と抽象化が可能になり、マルチスレッド・プログラミングがより理解しやすく、しかも間違いが起こりにくいものになります。一方、一部の言語ではマルチスレッド・プログラミングのサポートがまったくなかったため、このサポートを追加しました。そのうちの 1 つが、XUL プログラマーにとって主要なプログラミング言語となっている JavaScript です。JavaScript 1.9.1 以降、XUL プラットフォームの JavaScript にマルチスレッド・プログラミングが登場しました。




上に戻る


かつての XUL でのスレッド

ここで皆さんの頭に浮かんでいる考えは、きっとこういうものでしょう。「ちょっと待った! XUL は Firefox とその拡張機能を支える主要な技術なのだから、まさか XUL アプリケーションが今までずっと単一スレッドであったはずがない」。その考えはもちろん当たっています。しかし、XUL がサポートするのは JavaScript だけではないことを思い出してください。XPCOM の強力さのおかげで、コンポーネントを実装するには、C++ をはじめとする他の言語も使用することができます。もちろん C++ はネイティブ・スレッドをサポートしているため、XUL アプリケーション内部からマルチスレッドを使用する手段としては今まで常に、C++ が使われてきました。それでも、XUL のネイティブ・プログラミング言語はこれまでずっと JavaScript でした。正確には、Mozilla の JavaScript フレーバーです。今まで、JavaScript には任意のスレッドを明示的に生成して管理する手段がありませんでしたが、そうした状況を変えたのが JavaScript 1.9.1 です。マイナー・リリースではあるものの、JavaScript 1.9.1 は重要な新機能、すなわち Workers API によるマルチスレッド化を追加しています。




上に戻る


Workers API

この JavaScript の Workers API は一体何なのかというと、Worker オブジェクトを使うことで、必要な数だけスレッドを生成し、これらのスレッドにあらゆるタスクを行わせることができます。アプリケーション内で 1 つ以上のスレッドを生成しなければならない状況はよくあります。このような状況の一般的なケースの 1 つとして挙げられるのは、何らかの周期的な処理を実行することです。周期的な処理を行うのは多くのアプリケーションにおいて一般的なことであり、開発者の多くは JavaScript の setInterval 関数を使用してこれに対応しています。この関数の唯一の問題は、関数の周期的な実行はメイン・スレッドで行われるため、その間、ユーザーからの操作が一切ブロックされることです。この関数の実行に少しでも長い時間がかかる場合には、その実行期間中、アプリケーションはフリーズしてしまいます。個別スレッドで実行するメリットは、ここにあります。

このことから、複数スレッドを使用するさらに一般的なケースとして、長時間実行される処理へと話がつながってきます。メイン・スレッドで長時間実行される処理はアプリケーションがフリーズする原因となるため、このような処理は必ず専用のスレッドで実行しなければなりません。実行時間の長い処理の例としては、大きい整数の素因数分解や、複雑なデータ構造からなる巨大なリストのソートなどが挙げられますが、これらの処理は XUL アプリケーションで実行される最も一般的なタスクとは言えません。それよりもよく行われているのは、Web サービスへのアクセスや、ローカル・ファイルシステムからの読み取りといった、何らかの入出力処理です。この記事では、このような処理を取り上げ、Workers API ではスレッドを生成できるだけでなく、生成したスレッドで任意の複雑なタスクを実行できることを明らかにします。通常は、このような処理の結果として UI に対して何らかの更新を行うことになりますが、これを別のスレッドから行うのでは非常に危険なことになりかねません。幸い、Workers API はこの点を考慮して設計されているため、困難なように思える状況を大幅に単純化してくれます。これから、Workers API をどのように利用できるかを調べるために、単純なアプリケーションのコンテキストで検討していきます。




上に戻る


味見用のアプリケーション

前述したように、マルチスレッド化の一般的な用途の 1 つは、インターネットで Web サービスにアクセスするといった、何らかの入出力処理です。JavaScript/XUL は今までも、ネットワーク処理のマルチスレッド化を行ってきました。それが、XMLHttpRequest です。Ajax という秘密のソースにより、ネットワーク I/O はバックグラウンド・スレッドで実行されるようにデフォルトで設定されます。呼び出し側ではコールバック関数を使っており、ネットワーク I/O が完了すると、このコールバック関数がメイン・スレッドで呼び出されます。しかし、バックグラウンド・スレッドを明示的に制御することはできません。これを制御するのはランタイムです。さらに複数のネットワーク処理が必要だとしたらどうなるでしょうか。その場合、これらのリクエストはランタイムによってシリアライズされることになります。このアプリケーションで行うのは、次のようなタスクです。つまり Workers API を介し、複数のスレッドを使用して、複数のリモート Web サービスに同時にアクセスします。そして、ユーザーが Google、Yahoo!、そして Microsoft® Bing の 3 つの検索エンジンから返された検索結果を、どの検索エンジンの結果であるかを隠して表示および比較することで、「目隠しで味見」できるようにします。




上に戻る


3 つの検索エンジン、3 つのスレッド

これから調べていくアプリケーションはごく単純なもので、ユーザーが 1 つ以上の検索語を入力すると、3 つの検索エンジンで検索を行います。そして、どの検索エンジンを使ったものかわからないように検索結果を表示するといったものです。まず始めに、ユーザー・インターフェースの XUL コードから取り掛かります (リスト 1 を参照)。


リスト 1. XUL の UI コード

<hbox>
    <label value="Enter" id="lbl"/>
    <textbox value="" id="term"/>
    <button label="Go!" onclick="search()" id="btn"/>
</hbox>

上記は、検索フォームを作成する単純な XUL コードです。このコードが作成する水平レイアウトには、3 つのウィジェットがあります。最初のウィジェットはラベル、2 番目のウィジェットはユーザーが検索語を入力するテキスト・ボックス、そして 3 番目のウィジェットはボタンです。ユーザーがこのボタンをクリックすると、検索用の関数が呼び出されます。この検索用の関数の中で、ワーカー・スレッドが生成されます (リスト 2)。


リスト 2. Worker の生成Workers

function search(){
    var keyword = document.getElementById("term").value;
    var workerScripts = ["google.js", "yahoo.js", "bing.js"];
    var worker = {};
    for (var i=0;i<workerScripts.length;i++){
        worker = new Worker(workerScripts[i]);
        worker.onmessage = function(event) {
            displayResults(event.data);
        };
        worker.postMessage(keyword);
    }
}

1.9.1 より前のバージョンの JavaScript をベースにした XUL を使用した場合、3 つすべてのリクエストをキューに入れるか、またはデータを効率的に取得するために XPCOM と C++ を使用することになります。しかし JavaScript の Worker オブジェクトを使用すれば、データを取得する複数のスレッドを生成するのはごく簡単な話となります。各 Worker オブジェクトはそのコンストラクター用にストリングを 1 つ引数に取ります。このストリングは、ワーカーの本体となる別の JavaScript ファイルの場所を示すものです。ワーカーのファイル内にあるスクリプトは専用のスレッドで実行されます。

この例で該当するスクリプトは、google.js、yahoo.js、bing.js の 3 つです。これらのスクリプトを配列に格納し、その配列を繰り返し処理します。配列に含まれたスクリプトごとに Worker オブジェクトを作成するには、ワーカーのスクリプトがある場所を指定するストリングを渡すだけで済みます。次に、作成されるワーカーのそれぞれに対して onmessage 関数を設定します。この関数は、ワーカーがデータをメイン・スレッドに送信するたびに呼び出されます。この例で onmessage 関数は、displayResults という別の関数に処理を委譲するだけです。最後に各検索エンジンで使用する検索語の名前を送信するために、それぞれの Worker オブジェクトで postMessages 関数を呼び出します。この呼び出しによってワーカー・スレッドが Web サービスにアクセスできるようになり、3 つすべての Web サービスが同時に呼び出されるというわけです。呼び出しが同時に行われるだけでなく、結果の処理も同じく並列に行われます。

この onmessage/postMessage のパラダイムでは、具体的に何が行われているのでしょうか。これは Workers API の背後にある基本的なスレッド化モデルです。大抵の人々が見慣れているいつものスレッド化モデルは、C++ や Java™ 技術、そしてその他多くのプログラミング言語で使われているモデルであり、情報のやり取りをするためにスレッドが共有メモリーを変更するのが通常となっています。この方法は効率的ですが、多くの複雑な点を考慮しなければならないことになります (セマフォーやミューテックスなど)。さらに、これらの複雑な考慮事項は UI プログラミングにも必要となります。複数のスレッドが UI を変更する場合には、いとも簡単に UI のロックや機能停止という事態を引き起こすからです。一方、JavaScript の Workers API ではこうしたあらゆる複雑さを XUL にもたらす代わりに、単純化したモデルを使用します。メッセージの受け渡しをベースとしたこのモデルは、ある意味、Erlang や Scala で使用されているアクター・モデルと似ています。

リスト 2 をもう一度見てみると、Worker インスタンスは生成されたスレッドのプロキシーとして機能しています。スレッドにメッセージを送信するには、Worker インスタンスで postMessage メソッドが呼び出されます。このメソッドにはどのようなオブジェクトでも渡すことができます。生成されたスレッドは当然、その親スレッドにメッセージを渡すことができます。メッセージを渡すときには、Worker インスタンスの onmessage メソッドが呼び出されます。このメソッドにはノー・オペレーションのデフォルト実装があるため、(スレッドから返される結果に関心がある場合は) これをオーバーライドしなければなりません。リスト 2 では、このデフォルト実装をオーバーライドするために、このメソッドが event というパラメーターを 1 つ引数に取る関数になるように設定しています。event は、生成されたスレッドから送信されるオブジェクトです。リスト 2 の関数の場合には、event の data プロパティーを抽出します。これが、スレッドによって送信される実際のデータだからです。そしてこのデータを、UI を更新するための別の関数に渡します。

以上は、親スレッド側の内容です。生成された子スレッドについてはどうかと言うと、リスト 2 に示されているように、生成されたスレッドにはそれぞれに個別の JavaScript ファイルが使用されます。リスト 3 に、生成されるスレッドのうち、Google の検索 API にアクセスするためのスレッドを記載します。


リスト 3. Google 検索ワーカー

onmessage = function(event){
    var keyword = event.data;
    var results = searchGoogle(keyword);
    postMessage(results);
}

function searchGoogle(keyword){
    var url = "http://ajax.googleapis.com/ajax/services/"+
        "search/web?v=1.0&rsz=large&q="+keyword;
    var xhr = new XMLHttpRequest();
    xhr.open("GET", this.url, false);
    xhr.send();
    var response = JSON.parse(xhr.responseText);
    var results = [];
    var result = {};
    var data = response.results;
    for (var i=0;i<data.length;i++){
        result.url = data[i].url;
        result.title = data[i].title;
        result.description = data[i].content;
        results.push(result);       
    }
    return results; 
}

リスト 3 のコードには、リスト 2 に記載したコードとの共通点があります。その 1 つは、このスレッドには onmessage メソッドがあることです。onmessage はメッセージがスレッドに送信されるたびに呼び出される関数です。リスト 2 を見てみると、Worker インスタンスの postMessage メソッドが呼び出されています。これによって、リスト 3 の onmessage 関数が呼び出され、postMessage に渡されたオブジェクトが、onmessage メソッドに渡されるオブジェクトの data プロパティーとなります。この data プロパティーが、この例で検索に使用されるキーワードです。

リスト 3 では、渡されたメッセージからキーワードが抽出されると、今度は searchGoogle 関数が呼び出されています。この関数は、Google の検索 API を使用してキーワードの検索結果を要求します。Web サービスを呼び出すために、標準的な JavaScript API の XMLHttpRequest を使用していることに注目してください。お気付きのように、open メソッドを使用してリクエストを送信するときには、3 つのパラメーターを指定しています。最後のパラメーターが指定しているのは、リクエストが非同期であるかどうかです。このパラメーターはデフォルトで true に設定されるため、このリクエストは非同期ということになります。非同期リクエストの場合、XMLHttpRequest インスタンスの onreadystatechange 関数をオーバーライドして非同期リクエストのコールバック関数を設定できるようにしなければなりませんが、ワーカー・スレッドからこのようなリクエストを行う場合には、その必要はありません。代わりに非同期フラグを false に設定して、同期呼び出しを行います。その結果、send メソッドは Web サービスからレスポンスが返されるまでブロックされることになるので、レスポンスを容易に処理することができます。

この例の場合、Google から返されるレスポンスは JSON 構造になっています。レスポンスに対して直接 eval コマンドを使うこともできますが、パーサーを使用したほうが確実です。JavaScript 1.9.1 には、json.org の標準 JSON パーサーも組み込まれているので、その構文解析メソッドをそのまま使用すれば、確実に JSON データを JavaScript オブジェクトへと構文解析することができます。コードの残りの部分は、Google から返された構造からデータを抽出し、共通構造に取り込んでいるにすぎません。このデータが、postMessage 関数によって親スレッドに返されます。ワーカー・スレッドの postMessage 関数が呼び出されると、(親スレッド内の) Worker インスタンスの onmessage メソッドが呼び出されて、ワーカー・スレッドからデータが送信されます。これが、リスト 2 でこの onmessage 関数を設定した理由です。他の Worker もそれぞれ、ほとんど同じように機能します。その一例として、リスト 4 に Bing 検索エンジンの場合のワーカー・スレッドを記載します。


リスト 4. Bing 検索ワーカー

onmessage = function(event){
    var keyword = event.data;
    var results = searchBing(keyword);
    postMessage(results);
}

function searchBing(keyword){
    var bingAppId = "YOUR APP ID GOES HERE";
    var url = "http://api.search.live.net/json.aspx?Appid="+ 
        bingAppId+"&query="+keyword+"&sources=web";
    var xhr = new XMLHttpRequest();
    xhr.open("GET", this.url, false);
    xhr.send();
    var response = JSON.parse(xhr.responseText);
    var results = [];
    var results = {};
    var data = response.SearchResponse.Web.Results;
    for (var i=0;i<data.length;i++){
        result.url = data[i].Url;
        result.title = data[i].Title;
        result.description = data[i].Description;
        results.push(result);       
    }
    return results; 
}

ご覧のように、上記のコードはリスト 3 のコードと非常によく似ています。このコードは onmessage/postMessage パラダイムを使用して、親スレッドに対するデータの送受信を行います。Web サービスの URL が多少異なっているのは当然のことです。Bing から返されるデータの構造も少し異なりますが、これはリスト 3 の共通構造にマッピングするだけの話です。ご想像のとおり、Yahoo! Worker の場合のコードも Google と Bing の Worker と非常によく似ています。多少異なる点と言えば、URL と、正規化データ構造へのマッピングくらいのものです (すべてのソース・コードについては、「ダウンロード」を参照)。データが正規化構造に取り込まれた後は、ユーザー・インターフェースを更新する共通の関数に渡すことができます。




上に戻る


まとめ

並行プログラミングは確実に、現在のアプリケーション、そして特に将来のアプリケーションが成功する鍵となります。アプリケーションが複数のスレッドを使用して環境を最大に利用すれば、卓越したユーザー・エクスペリエンスを実現することができます。この記事ではごく単純なサンプル・アプリケーションを用いて、よく使われている 3 つの検索エンジンで検索を行いましたが、検索エンジンごとに順に処理を繰り返すのではなく、3 つのエンジンから同時に結果を取得することによって、どれだけユーザー・エクスペリエンスが改善されるかは想像できるはずです。シングル CPU のマシンでさえも、Web サービスからのレスポンスを長い時間、無駄に待つことがなくなるので、大きな違いが生まれてくるでしょう。JavaScript 1.9.1 に追加された Workers API は、あらゆる XUL ベースのアプリケーションで、このようなプログラミングを簡単に行えるようにします。この記事で紹介した手法は、Firefox 拡張機能や XULRunner を使用するデスクトップ・アプリケーションに適用することができます。簡単に使用できる Workers API により、ロックを心配することなく複数のスレッドを生成してください。XUL アプリケーションの能力を高めるためにマルチスレッド化を使用しないことへの言い訳は、Workers API によって一切通用しなくなります。





上に戻る


ダウンロード

内容ファイル名サイズダウンロード形式
Search engine test sourcexul-workers.zip3KBHTTP
ダウンロード形式について


参考文献

学ぶために
  • An introduction to XML User Interface (XUL) development」(Michael Galpin 著、developerWorks、2008年11月): XUL を使い始めたばかりの開発者にとって、XUL ベースのブログ・エディターを作成する手順を説明するこのチュートリアルは Web 開発のスキルを強化する出発点となります。XUL でのデスクトップ・アプリケーション開発に役立つツールについて学んでください。

  • XUL を使ったブラウザー拡張機能」(Uche Ogbuji 著、developerWorks、2007年10月): Firefox 拡張機能でいかに並行性が役立つか、そして Firefox の拡張機能を作成するのがいかに容易になったかを調べてください。

  • Create Web applets with Mozilla and XML」(Nigel McFarlane 著、developerWorks、2003年10月): XUL で高度な Web ページを作成する方法を学んでください。

  • XPCOM 入門」(Rick Parrish 著、developerWorks、2001年2月): かつて XUL で唯一のマルチスレッド化手段であった XPCOM の全容を学んでください。

  • 多忙な Java 開発者のための Scala ガイド: Scala での並行性を探る」(Ted Neward 著、developerWorks、2009年2月): この入門記事で、JavaScript の Workers と多くの共通点を持つ Scala で使用されているアクター・モデルについて学んでください。

  • An Introduction to XUL Part 1: XUL についてのわかりやすい調査と、単純なテキスト・エディターでユーザー・インターフェースを作成する方法を読んでください。

  • Mozilla developer center: オンラインと接続してもしなくても実行できる、カスタマイズ可能で充実した機能を備えたクロスプラットフォーム・アプリケーションを作成するための XUL の情報源にアクセスしてください。

  • 多忙な Java 開発者のための Scala ガイド: オブジェクト指向のための関数型プログラミング」(Ted Neward 著、developerWorks、2008年1月): Scala が関数型プログラミングとオブジェクト指向プログラミングの両方に最適な理由を説明しています。Scala の全容を探るには、この連載の記事をすべて読んでください。

  • Scala と XML」(Michael Galpin 著、developerWorks、2008年4月): Scala によって、XML の処理をはじめとした多くの共通プログラミング・タスクがいかに簡単になるかを調べてください。

  • IBM XML 認定: XML や関連技術の IBM 認定技術者になる方法について調べてください。

  • developerWorks XML ゾーン: 開発者が効果的かつ効率的な Web アプリケーションを作成する際に XML の威力を利用できるようにするための記事およびチュートリアルを一堂に集めたゾーンにアクセスしてください。

  • XML Technical library: 広範な技術に関する記事とヒント、チュートリアル、標準、そして IBM Redbooks については、developerWorks XML ゾーンを参照してください。

  • developerWorks の Technical events and webcasts: これらのセッションで最新情報を入手してください。

  • Technology bookstore: この記事で紹介した技術やその他の技術に関する本を参照してください。

  • developerWorks podcasts: ソフトウェア開発者向けの興味深いインタビューとディスカッションを聞いてください。


製品や技術を入手するために
  • Firefox 3.5: このオープンソース Web ブラウザーを入手してください。

  • Mozilla XULRunner 1.9.1: この Mozilla ランタイム・パッケージをダウンロードして、Firefox や Thunderbird に並ぶリッチな XUL+XPCOM アプリケーションを実現するために活用してください。

  • IBM 製品の試用版: DB2®、Lotus®、Rational®、Tivoli®、および WebSphere® のアプリケーション開発ツールとミドルウェア製品を体験するには、試用版をダウンロードするか、IBM SOA Sandbox のオンライン試用版を試してみてください。


議論するために


著者について

Michael Galpin's photo

Michael Galpin は eBay のアーキテクトです。彼は developerWorks に頻繁に寄稿しており、また TheServerSide.com や Java Developer's Journal、そして彼自身のブログにも記事を執筆しています。彼は 1998年からプロとしてプログラミングを行っており、California Institute of Technology で数学の学位を取得しています。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



 


 


不充分・不完全である大変素晴らしい
 


この記事を共有する

del.icio.us del.icio.us newsing newsing FC2ブックマーク FC2ブックマーク
Choix! Choix! ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
MM/memo MM/memo CZブックマーク CZブックマーク livedoorクリップ livedoorクリップ
はてなブックマーク はてなブックマーク Buzzurl(バザール) Buzzurl(バザール)




上に戻る


    日本IBMについて プライバシー お問い合わせ