検索
特集

スレッドの落とし穴(5/6 ページ)

Windowsプログラマーでスレッドをいちども使ったことがない人はいないだろう。CPUのマルチコア化によって、ソースコードが上から順に実行という定説がなくなったのは久しい。このdev .NET特集では、知らぬと怖いテクニックを解説する。

PC用表示 関連情報
Share
Tweet
LINE
Hatena
  • Interlockedクラス

 あるニュースグループで「lockにタイムアウトを指定できればよい」という提案があった。一定時間以内にlockできなければ諦める、というロジックだ。

 ほかのスレッドが何かをしている場合に取り得る別の手段がある場合には、Interlockedクラスを使おう。

*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***

 分割不可能な操作として比較と設定を行ってくれるため、このコードはスレッドセーフになる。一定時間待つなどの高度な機能は持たないが、Interlockedはlockなど、ほかの手段と比べてパフォーマンスに与える影響が非常に小さい。ほかの手段は、相当数のコードが実行されるが、Interlockedによって増えるコードはx86においてはlockプレフィックスの1バイトだけだ。

 このためまずは、Interlockedクラスで用が足りないか検討し、足りない場合にのみほかの解決策を検討するべきだ。

  • ReaderWriterLockクラス

 逆にもっと多機能なスレッド同期が必要な場合には、ReaderWriterLockクラスがよい。

 このクラスはlock/SyncLockが内部的に利用するMonitorクラスと比べて大きく以下の利点を持つ。

1. 読み込みロックと書き込みロックを区別することで、複数スレッドからの同時読み込みをサポートすることができる。

2. ロック数にかかわらず、現在のスレッドが持っているロックを完全に開放することができる。

 最初に挙げた利点は、並列性を上げることでパフォーマンスの向上に役立つ。

 次の利点は、より安全なコードに向けた配慮ができる。サンプルコードでその違いを見てみよう。C#におけるlock、あるいはVB.NETにおけるSyncLockは、内部では以下のコードに展開される。

*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***

 十分に安全そうに思えるが、数少ない例外としてfinally節が実行されない場合がある。例えばStackOverflowExceptionが発生した場合には、finally節は実行されない。

 筆者は、StackOverflowExceptionなどは、バグで無限再帰にでもならない限り起きないよ、と思っていたが、実はそうでもないことを最近になって身をもって知った。ADO.NETを使った以下のコードを見てほしい。

*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***

 DataView.RowFilterに式を設定すると、ADO.NETの中でその式の解析が行われるが、そこで再帰が使われているため、ORやANDをたくさん並べた複雑な式の場合にはこの行でStackOverflowExceptionが発生する。

 その場合、finally節が実行されないためlockが解放されずにスレッドが終了してしまうため、ほかのスレッドはlockを永久に取得できなくなる。プログラムとしては、ハングアップの状態だ。

 問題の根本としては、lockがtry/finallyに展開されることと、finallyが常に実行されるわけではないことに起因している。このため、lockやtry/finallyに依存せずに、Monitorとtry/catchを用いて書けば避けることができるのだ。実際、lock/SyncLockがtry/catchに展開されないのは、仕様として間違っているのではないかとも思われる。次のコードは安全だ。

*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***

 しかし、どこでStackOverflowExceptionが発生するのかは事前には分からない。より安全なコードとして、上位でスレッドが終了する時にそのスレッドが持っているロックをきちんと開放してくれれば、プログラムにほかのバグがあった時でも、被害を抑えることができる。

*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***

 ReaderWriterLock.ReleaseLockメソッドは、ロックの取得回数にかかわらず、現在のスレッドで保持しているロックを解放してくれる。

 ロック保持状態を取得するIsReaderLockHeldやIsWriterLockHeldプロパティもあるため、ロックしたまま終了しよとするスレッドがあれば、Assertを起こすことでバグの早期発見に役立てることもできるだろう。

 小さいクラス内のスレッド同期には向かないかもしれないが、スレッドを利用するアプリケーションレベルではぜひ入れておきたい安全策だ。

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る