The Backyard - FailedDreamOrMultitaskingGuiTool Diff
- Added parts are displayed like this.
- Deleted parts are displayed
like this.
http://weblogs.java.net/blog/kgh/archive/2004/10/multithreaded_t.html
訳:新丈 径
""「夢の跡」というのは非常によいのですが、なかなか埋め込むのがタイヘンなので「見果てぬ夢」に変更。てきとー訳。だいたいこんな感じであってるのかしら。
---
!マルチスレッドツールキット:見果てぬ夢?
少し前のこと。ふと疑問が浮かんだ。「Swingをマジでマルチスレッド化するべきなんじゃないか?」 個人的な答えをいうならば「No」だ。それはなぜだろう。
!!見果てぬ夢
「見果てぬ夢」とでもいうようなアイデアがコンピュータサイエンスの世界には少なからずある(「見果てぬ夢」というのはVernor Vingeから拝借)。一見すると、「見果てぬ夢」はすばらしいアイデアに思える。だから、時が経つと、またこれらの夢は蘇る。そして、また、人々は多くの時間を夢に費やし、夢を実現すべく多くのことを考える。だいたいの場合、研究段階では万事快調だから、製品段階でもことがうまく進むかのような幻惑にとらわれてしまう。だが、すべてがまーるく収まるなんてことはまったくない。
筆者の場合、マルチスレッド化されたGUIツールキットが「見果てぬ夢」の1つに当てはまる。マルチスレッド環境なんだから、GUIツールキットをマルチスレッド化するというのはまったくもって正しいことのように思える。どんなスレッドであってもボタンやテキストフィールドなどなどのGUIステートを更新できるべきだ。くそったれ。ちょっとしたロックの問題なだけなのに、なんでこうもダメなんだ?オーケー。バグがあるんだ。だが、コレは直せる。だろ? 残念だけど、コトはそんなにシンプルにはならないんだ。
これまでの経験からいえるのは、マルチスレッド化されたGUIは笑っちゃうくらいにデッドロックにハマリまくって、競合しまくる傾向にあるということだ。この難題についてヒソヒソ話が聞こえてきたのは、80年代初期のXerox PARCでCedar GUIライブラリを開発していた連中からだ。彼らは超スマートで、スレッドについてホントによく知っていたから、GUIで頻繁にデッドロックにはまり込んでいるなんて言われても信じられなかった。むしろ、これはデータに問題があるか、例外的な状況のことだと思えたんだ。
残念ながら何年経ったところで、いつもお決まりのパターンが繰り返された。マルチスレッドの世界へと何度も旅立って、亀のような歩みでもってイベントキューモデルに辿りつくことが何度も繰り返されるだけ。「イベントスレッドにGUI処理をさせるのが一番なんだよ」
AWTでこの目に遭った。最初こそ、AWTは何も変わったところのないJavaのマルチスレッドライブラリのように見えた。でも、JavaチームがAWTで経験を積んで、誰もが経験したデッドロックと競合に遭遇したことで、みんなわかってきたんだ。できもしない約束を守ろうとしているってコトに。
1997年に行われたSwingのデザインレビューの1つで、この分析は最高潮に達した。このとき、僕らはAWTの現状と、これまでに業界がどんな目に遭ってきたかを検討した。結果、Swingでのマルチスレッドサポートは極めて限定されたものにすべきである、というSwingチームの提案を受け入れることになった。ほんのわずかな例外を除けば、GUIツールキット処理はすべてイベント処理スレッドで行われるべきである。そうでないスレッドが GUIステートを直接扱おうなんてことはこれっぽっちも考えてはいけない。
!!なんでソーハード?
1995年に、John OusterhoutはイベントvsスレッドについてUsenixで意義深い議論を巻き起こした。スレッド駆動プログラミングとイベント駆動プログラミングの間でのいくつかのトレードオフについてさまざまな議論が繰り広げられた。そして、マルチスレッドプログラミングが難しくなり、イベント駆動プログラミングだとシンプルにやれるのか、その理由をいくつも正しく指摘した。すべてのプログラムという面では、必ずしも彼の見解には賛成できないこともあるが、しかし、GUIプログラムについては完全に同意する。
筆者には、スレッドにまつわるGUIツールキットの問題点のいくつかは、入力イベント処理と抽象化のミスマッチに原因があるように思える。
入力イベント処理の問題とはつまり、これがGUIで行われる処理とは正反対の方向に実行される傾向にあるということだ。一般的にいって、GUI処理というのは、抽象化されたライブラリの上部から始まって、「下部」へと処理が進んでいく。何かのGUIオブジェクトで表現されるアプリケーションで抽象的なアイデアをあれこれしているとしよう。すると、アプリケーションをスタート地点として、ライブラリの上位に位置するGUI抽象が呼び出される。ここから、ライブラリの下層にあるGUI抽象が呼び出され、こいつはわけわからん実装のツールキットを呼び出して、そこからOSへとたどり着くわけだ。これとは正反対に、入力イベントはOSレイヤーから始まって、ディスパッチをされながら、徐々に抽象レイヤーへと上っていく。で、最後にアプリケーションコードにたどり着く。
さて、抽象を使っているので、おのずと個々の抽象の内部ではロックが使われることになる。そして、残念ながら、ロックの順番――2つの処理が、反対の順序でロックを獲得しようとする――という古典的な悪夢に襲われる。つまり、デッドロックは不可避である。
この問題は、はじめのうちはいくつかのスレッド処理のバグの形で現れる。だから、最初はロックの挙動に手を入れてこれらの問題をなんとかしようとする。ここでロックを解放してやって、ここではもっと上手にロック機構を使えばいいんだよ。そう。これはとっても楽しい。でも、こいつらは寄せては引き、引いては押し寄せてくるんだから、いちいち相手をするのは無理だ。上手にロック機構を使うってのは、だいたいが(ロックを使わないために)うまくいったりいかなかったりの競合の詰め合わせセットになるか、(頭のいいヤツが複雑に込み入ったロックの使い方をしたばっかりに)頭のいいヤツでなければ考えられないような複雑に込み入ったデッドロックへと姿を変える。95〜97年には、こういうのが大量に発生したもんだ。
問題はGUIツールキットレイヤーを超越して、なお、これがツールキットレイヤーとアプリケーションレイヤーの間で発生していることに気がついた?問題があまりに難しいので、GUIレイヤーでのすべての処理にシングルロックを適用したくなるかもしれない。でも、やっぱり同じ問題が出るだけだ。
じゃあ、解決策は? 問題を俯瞰してみると、「下から上」にいきたがるスレッドと、「上から下」にいきたがるスレッドの間には根本的に相容れない部分があることがわかる。個々のバグをちまちまつぶしている間は、全体としてのこの状況は直せない。
こうした理由で、Swingチーム、そしてメジャーなGUIツールキットのほとんどでは「すべてのGUI処理をシングルイベントスレッドで実行する」という解決策にたどり着いたわけである。ある意味、これはすべてのGUI処理はイベント駆動され、「上から下」にいきたがるスレッドは単に新しいイベントの1つとして扱われることを意味している。
これは確かにうまく機能した。複雑なGUIアプリを作っても、安定動作をしたんだ。イヤッホー!でも、実行に時間がかかる処理の管理が難しくなってしまった。筆者は、ちょっとしたSwingプログラムを書いて、どうでもよいのにサイズだけが巨大な添付ファイルをe-mailアーカイブから削除するために定期的に使っていた。何十メガバイトものe-mailの山を読み出している間、GUIにハングしてほしくはない。さらに、進行状況も画面に表示してほしい。そういうわけで、最終的には長大な処理はいくつかのワーカースレッドに分割し、GUI処理は元のままイベントスレッドで実行するようにして、これらの間でのバランスを取ることに細心の注意を払うことになった。魔法のマルチスレッドライブラリがあれば、もっとシンプルにできたのかもしれないが、それでもこれには安定して動作するという明らかなご利益がある。
!!巧の技
ものごとにはホントにシロクロつくものなんだろうか? マルチスレッドなツールキットをうまく使いこなせた人なんてホントにいるんだろうか? そりゃいるよ。でも、これは「見果てぬ夢」が持つ特徴の1つをあらわすものだと筆者は思う。
マルチスレッドGUIツールキットを使ってうまくプログラミングすることは可能だと思う。ただし、そのツールキットがとても慎重にデザインされていて、その血みどろのロッキング手法を仔細に公開してくれていれば。そしてプログラマは頭がすっごくよくなきゃならないし、慎重になる必要もあるし、ツールキットの全体構造をすべて理解している必要がある。少しでも間違いがあれば、多くの場合はちゃんと動作しても、たまにハングしたり(デッドロック)、意図せぬ動作をしたりする(競合)ことになるだろう。ツールキットのデザインにどっぷりと浸かった人がいるとすれば、マルチスレッドというアプローチは最適な手段となる。
不幸だと思うのは、商的な利用に関しては、こうした特性を期待することはできないことにある。だいたいの人がなれるのはせいぜい普通に頭のよいプログラマでしかなくって、そんなヤツらに作れるのは意味不明の原因で不安定な動作をするアプリケーションでしかない。だから、そういう開発者というのは、頭が悪い実装をされているツールキットを使ってはイライラと不満を募らせて罵声を浴びせることになる(ゴメンゴメン!これははじめてAWTを使ったときの筆者のことだ)。
もう1つの妙案:イベントスレッドを複数使えば、Java VM中で複数のGUI処理を同時に動かすことができる。別々の処理に分けられた作業はほとんど分離された環境に置かれ、それぞれが別個のGUIを持つ(コンポーネントを共有することもないし、階層が混在することもない)。こうなれば、最低限のロック機構を使うだけで、ツールキットの最下層のレベルからイベントを正しいイベントスレッドへとディスパッチできるようになる。1つのJVMで複数のアプレットを実行させる場合なんかにこれは使える。が、これは何にでも使える解法というわけではない。ほんとどのアプリケーションでは、イベントスレッドは1つだけ、という制約の元で動作する必要があるのだ。
ここまで、Swingをはじめとするツールキットがシングルスレッドでなければならない理由を見てきた。少し前のChetのブログには、これに関連して、マルチスレッドによりユーザープログラムが複雑化し、多くの場合、生のグラフィック性能を向上させる助けとはならない理由について書かれたトピックが掲載されている。
忘れる前に書いておこう。「processes and monitors are duals」であることを思い出す人もいるかもしれない。確かにそうだ。これは正しい。イベントスレッドを使うということは、グローバルロックを実装しているともいえる。逆に捉えると、イベントキューに等価なグローバルロックを作成しているともいえる。これはまったくもって不細工なことで、たくさんのことを考慮する必要があり、多くの抽象の土台を脅かす。しかし、より大きな問題は、Java開発者は複数のロックを使用することが多く、イベントキューモデルとの等価性を確保したければ、複数のロックをどう扱えばよいかについて、いくつもの不明瞭なルールに従わなければならないという点にある。イベントキューモデルでは、その中核となるただ1つのロックがよりハッキリと明確なものとなり、総体としては、安心してモデルを使用し、安定して動作するGUIプログラムを構築する助けとなるのである。
!!まとめ
まとめると、他の人たちと同様、筆者も柔軟性があり、強力な、真のマルチスレッドGUIツールキットをホントに見てみたいのだ。だが、そこに辿りつく方法がわからない。これまでの経験からは、こうすればマルチスレッド化は成功すると誰もが一度は考えるやり方ではうまくいかないという強力な根拠がある。将来的には、まったく新しくて、よりよい方法が見つかるかもしれないが、現在のところはイベントが頼りというのがその答えのようだ。
− Graham
!!!権利関係
""元のGrahamの文章は[[Attribution-Noncommercial-Share 2.0 Generic|http://creativecommons.org/licenses/by-nc-sa/2.0/]]です。この文章も、日本版クリエイティブコモンズの同等のものに合わせて [[表示−非営利−継承|http://creativecommons.org/licenses/by-nc-sa/2.1/jp/]] です。
!!!グラハム・ハミルトンのGraham Hamiltonの著作
[[JDBCによるデータベースアクセス (JAVAシリーズ) |http://www.amazon.co.jp/gp/product/4756118615?ie=UTF8&tag=leclatdesjour-22&linkCode=as2&camp=247&creative=7399&creativeASIN=4756118615]]
[[http://ec2.images-amazon.com/images/I/31A4aIjsalL._SL500_AA188_.jpg]]
訳:新丈 径
""「夢の跡」というのは非常によいのですが、なかなか埋め込むのがタイヘンなので「見果てぬ夢」に変更。てきとー訳。だいたいこんな感じであってるのかしら。
---
!マルチスレッドツールキット:見果てぬ夢?
少し前のこと。ふと疑問が浮かんだ。「Swingをマジでマルチスレッド化するべきなんじゃないか?」 個人的な答えをいうならば「No」だ。それはなぜだろう。
!!見果てぬ夢
「見果てぬ夢」とでもいうようなアイデアがコンピュータサイエンスの世界には少なからずある(「見果てぬ夢」というのはVernor Vingeから拝借)。一見すると、「見果てぬ夢」はすばらしいアイデアに思える。だから、時が経つと、またこれらの夢は蘇る。そして、また、人々は多くの時間を夢に費やし、夢を実現すべく多くのことを考える。だいたいの場合、研究段階では万事快調だから、製品段階でもことがうまく進むかのような幻惑にとらわれてしまう。だが、すべてがまーるく収まるなんてことはまったくない。
筆者の場合、マルチスレッド化されたGUIツールキットが「見果てぬ夢」の1つに当てはまる。マルチスレッド環境なんだから、GUIツールキットをマルチスレッド化するというのはまったくもって正しいことのように思える。どんなスレッドであってもボタンやテキストフィールドなどなどのGUIステートを更新できるべきだ。くそったれ。ちょっとしたロックの問題なだけなのに、なんでこうもダメなんだ?オーケー。バグがあるんだ。だが、コレは直せる。だろ? 残念だけど、コトはそんなにシンプルにはならないんだ。
これまでの経験からいえるのは、マルチスレッド化されたGUIは笑っちゃうくらいにデッドロックにハマリまくって、競合しまくる傾向にあるということだ。この難題についてヒソヒソ話が聞こえてきたのは、80年代初期のXerox PARCでCedar GUIライブラリを開発していた連中からだ。彼らは超スマートで、スレッドについてホントによく知っていたから、GUIで頻繁にデッドロックにはまり込んでいるなんて言われても信じられなかった。むしろ、これはデータに問題があるか、例外的な状況のことだと思えたんだ。
残念ながら何年経ったところで、いつもお決まりのパターンが繰り返された。マルチスレッドの世界へと何度も旅立って、亀のような歩みでもってイベントキューモデルに辿りつくことが何度も繰り返されるだけ。「イベントスレッドにGUI処理をさせるのが一番なんだよ」
AWTでこの目に遭った。最初こそ、AWTは何も変わったところのないJavaのマルチスレッドライブラリのように見えた。でも、JavaチームがAWTで経験を積んで、誰もが経験したデッドロックと競合に遭遇したことで、みんなわかってきたんだ。できもしない約束を守ろうとしているってコトに。
1997年に行われたSwingのデザインレビューの1つで、この分析は最高潮に達した。このとき、僕らはAWTの現状と、これまでに業界がどんな目に遭ってきたかを検討した。結果、Swingでのマルチスレッドサポートは極めて限定されたものにすべきである、というSwingチームの提案を受け入れることになった。ほんのわずかな例外を除けば、GUIツールキット処理はすべてイベント処理スレッドで行われるべきである。そうでないスレッドが GUIステートを直接扱おうなんてことはこれっぽっちも考えてはいけない。
!!なんでソーハード?
1995年に、John OusterhoutはイベントvsスレッドについてUsenixで意義深い議論を巻き起こした。スレッド駆動プログラミングとイベント駆動プログラミングの間でのいくつかのトレードオフについてさまざまな議論が繰り広げられた。そして、マルチスレッドプログラミングが難しくなり、イベント駆動プログラミングだとシンプルにやれるのか、その理由をいくつも正しく指摘した。すべてのプログラムという面では、必ずしも彼の見解には賛成できないこともあるが、しかし、GUIプログラムについては完全に同意する。
筆者には、スレッドにまつわるGUIツールキットの問題点のいくつかは、入力イベント処理と抽象化のミスマッチに原因があるように思える。
入力イベント処理の問題とはつまり、これがGUIで行われる処理とは正反対の方向に実行される傾向にあるということだ。一般的にいって、GUI処理というのは、抽象化されたライブラリの上部から始まって、「下部」へと処理が進んでいく。何かのGUIオブジェクトで表現されるアプリケーションで抽象的なアイデアをあれこれしているとしよう。すると、アプリケーションをスタート地点として、ライブラリの上位に位置するGUI抽象が呼び出される。ここから、ライブラリの下層にあるGUI抽象が呼び出され、こいつはわけわからん実装のツールキットを呼び出して、そこからOSへとたどり着くわけだ。これとは正反対に、入力イベントはOSレイヤーから始まって、ディスパッチをされながら、徐々に抽象レイヤーへと上っていく。で、最後にアプリケーションコードにたどり着く。
さて、抽象を使っているので、おのずと個々の抽象の内部ではロックが使われることになる。そして、残念ながら、ロックの順番――2つの処理が、反対の順序でロックを獲得しようとする――という古典的な悪夢に襲われる。つまり、デッドロックは不可避である。
この問題は、はじめのうちはいくつかのスレッド処理のバグの形で現れる。だから、最初はロックの挙動に手を入れてこれらの問題をなんとかしようとする。ここでロックを解放してやって、ここではもっと上手にロック機構を使えばいいんだよ。そう。これはとっても楽しい。でも、こいつらは寄せては引き、引いては押し寄せてくるんだから、いちいち相手をするのは無理だ。上手にロック機構を使うってのは、だいたいが(ロックを使わないために)うまくいったりいかなかったりの競合の詰め合わせセットになるか、(頭のいいヤツが複雑に込み入ったロックの使い方をしたばっかりに)頭のいいヤツでなければ考えられないような複雑に込み入ったデッドロックへと姿を変える。95〜97年には、こういうのが大量に発生したもんだ。
問題はGUIツールキットレイヤーを超越して、なお、これがツールキットレイヤーとアプリケーションレイヤーの間で発生していることに気がついた?問題があまりに難しいので、GUIレイヤーでのすべての処理にシングルロックを適用したくなるかもしれない。でも、やっぱり同じ問題が出るだけだ。
じゃあ、解決策は? 問題を俯瞰してみると、「下から上」にいきたがるスレッドと、「上から下」にいきたがるスレッドの間には根本的に相容れない部分があることがわかる。個々のバグをちまちまつぶしている間は、全体としてのこの状況は直せない。
こうした理由で、Swingチーム、そしてメジャーなGUIツールキットのほとんどでは「すべてのGUI処理をシングルイベントスレッドで実行する」という解決策にたどり着いたわけである。ある意味、これはすべてのGUI処理はイベント駆動され、「上から下」にいきたがるスレッドは単に新しいイベントの1つとして扱われることを意味している。
これは確かにうまく機能した。複雑なGUIアプリを作っても、安定動作をしたんだ。イヤッホー!でも、実行に時間がかかる処理の管理が難しくなってしまった。筆者は、ちょっとしたSwingプログラムを書いて、どうでもよいのにサイズだけが巨大な添付ファイルをe-mailアーカイブから削除するために定期的に使っていた。何十メガバイトものe-mailの山を読み出している間、GUIにハングしてほしくはない。さらに、進行状況も画面に表示してほしい。そういうわけで、最終的には長大な処理はいくつかのワーカースレッドに分割し、GUI処理は元のままイベントスレッドで実行するようにして、これらの間でのバランスを取ることに細心の注意を払うことになった。魔法のマルチスレッドライブラリがあれば、もっとシンプルにできたのかもしれないが、それでもこれには安定して動作するという明らかなご利益がある。
!!巧の技
ものごとにはホントにシロクロつくものなんだろうか? マルチスレッドなツールキットをうまく使いこなせた人なんてホントにいるんだろうか? そりゃいるよ。でも、これは「見果てぬ夢」が持つ特徴の1つをあらわすものだと筆者は思う。
マルチスレッドGUIツールキットを使ってうまくプログラミングすることは可能だと思う。ただし、そのツールキットがとても慎重にデザインされていて、その血みどろのロッキング手法を仔細に公開してくれていれば。そしてプログラマは頭がすっごくよくなきゃならないし、慎重になる必要もあるし、ツールキットの全体構造をすべて理解している必要がある。少しでも間違いがあれば、多くの場合はちゃんと動作しても、たまにハングしたり(デッドロック)、意図せぬ動作をしたりする(競合)ことになるだろう。ツールキットのデザインにどっぷりと浸かった人がいるとすれば、マルチスレッドというアプローチは最適な手段となる。
不幸だと思うのは、商的な利用に関しては、こうした特性を期待することはできないことにある。だいたいの人がなれるのはせいぜい普通に頭のよいプログラマでしかなくって、そんなヤツらに作れるのは意味不明の原因で不安定な動作をするアプリケーションでしかない。だから、そういう開発者というのは、頭が悪い実装をされているツールキットを使ってはイライラと不満を募らせて罵声を浴びせることになる(ゴメンゴメン!これははじめてAWTを使ったときの筆者のことだ)。
もう1つの妙案:イベントスレッドを複数使えば、Java VM中で複数のGUI処理を同時に動かすことができる。別々の処理に分けられた作業はほとんど分離された環境に置かれ、それぞれが別個のGUIを持つ(コンポーネントを共有することもないし、階層が混在することもない)。こうなれば、最低限のロック機構を使うだけで、ツールキットの最下層のレベルからイベントを正しいイベントスレッドへとディスパッチできるようになる。1つのJVMで複数のアプレットを実行させる場合なんかにこれは使える。が、これは何にでも使える解法というわけではない。ほんとどのアプリケーションでは、イベントスレッドは1つだけ、という制約の元で動作する必要があるのだ。
ここまで、Swingをはじめとするツールキットがシングルスレッドでなければならない理由を見てきた。少し前のChetのブログには、これに関連して、マルチスレッドによりユーザープログラムが複雑化し、多くの場合、生のグラフィック性能を向上させる助けとはならない理由について書かれたトピックが掲載されている。
忘れる前に書いておこう。「processes and monitors are duals」であることを思い出す人もいるかもしれない。確かにそうだ。これは正しい。イベントスレッドを使うということは、グローバルロックを実装しているともいえる。逆に捉えると、イベントキューに等価なグローバルロックを作成しているともいえる。これはまったくもって不細工なことで、たくさんのことを考慮する必要があり、多くの抽象の土台を脅かす。しかし、より大きな問題は、Java開発者は複数のロックを使用することが多く、イベントキューモデルとの等価性を確保したければ、複数のロックをどう扱えばよいかについて、いくつもの不明瞭なルールに従わなければならないという点にある。イベントキューモデルでは、その中核となるただ1つのロックがよりハッキリと明確なものとなり、総体としては、安心してモデルを使用し、安定して動作するGUIプログラムを構築する助けとなるのである。
!!まとめ
まとめると、他の人たちと同様、筆者も柔軟性があり、強力な、真のマルチスレッドGUIツールキットをホントに見てみたいのだ。だが、そこに辿りつく方法がわからない。これまでの経験からは、こうすればマルチスレッド化は成功すると誰もが一度は考えるやり方ではうまくいかないという強力な根拠がある。将来的には、まったく新しくて、よりよい方法が見つかるかもしれないが、現在のところはイベントが頼りというのがその答えのようだ。
− Graham
!!!権利関係
""元のGrahamの文章は[[Attribution-Noncommercial-Share 2.0 Generic|http://creativecommons.org/licenses/by-nc-sa/2.0/]]です。この文章も、日本版クリエイティブコモンズの同等のものに合わせて [[表示−非営利−継承|http://creativecommons.org/licenses/by-nc-sa/2.1/jp/]] です。
!!!
[[JDBCによるデータベースアクセス (JAVAシリーズ) |http://www.amazon.co.jp/gp/product/4756118615?ie=UTF8&tag=leclatdesjour-22&linkCode=as2&camp=247&creative=7399&creativeASIN=4756118615]]
[[http://ec2.images-amazon.com/images/I/31A4aIjsalL._SL500_AA188_.jpg]]