ロック(ミューテックス)の再入可能性 [プログラマー現役続行]
Go言語で提供されている
私自身がマルチスレッドプログラミングを本格的に行ったのは、Solaris 2.3を用いていたFuji Xerox DocuStation IM 200の開発です。当時、Solaris 2.3で提供されていたC言語用のスレッドライブラリ(POSIX Threadが登場する前です)は、再入可能なロックは提供していませんでした。そのためデッドロックが発生すると、場合によってはC++のメンバー関数の分割を行ったものです。privateのロックを獲得しないメンバー関数と、ロックを獲得してprivateのメンバー関数を呼ぶpublicの公開用のメンバー関数へと分割します。この分割については、上記の『プログラミング言語Go』にも書かれています。
IM 200が商品化された後の1996年にJavaを学び始めた時に、ロックが再入可能だというのは非常に魅力的に思えました。Javaの影響を受けて、2000年に設計したC++用のスレッドライブラリ(「clibと呼ばれるライブラリー開発の思い出」)では、再入可能なロックを提供する
2003年から2009年に従事したコントローラソフトウェア開発では、clibを用いてマルチスレッドプログラミングをかなり行ったのですが、再入可能であるために起きる問題に悩まされて、マルチスレッドプログラミングの難しさを痛感したものです。幸い、その開発は完全なテスト駆動開発でしたので、「マルチスレッドプログラミングにおける重要な4要件」を満たしており、様々な問題を解決する経験を積むことができました。このソフトウェア開発で経験した問題は、『Effective Java 第3版』でJoshua Blochが述べていることそのものでした。
活性エラーはデッドロックとして表面化しますが、安全性エラーはコードが動作しているように見えるが正しく動作していないという問題を引き起こします。特に競合状態(race condition)により引き起こされる問題は、現象の再現性も低く、調査が非常に難しいことが多いです。
2013年7月から2015年5月まで行ったコントローラソフトウェア開発は、私にとっては4回目のコントローラソフトウェア開発でしたが、マルチスレッドプログラミングの複雑さをGo言語のゴルーチンとチャネルで軽減できないかの検証も狙っていました。しかし、やはりデジタル複合機のコントローラソフトウェアは複雑であり、デッドロックはよく発生しましたし、競合状態も発生しました。幸い、Go言語には競合検出機能が提供されているので、その点は助かりました。
現在、従事しているソフトウェア開発では、再入可能性が問題となるような複雑なソフトウェア開発は行っていません。その点では、4回のデジタル複合機のコントローラソフトウェア開発は、私に貴重な経験を与えてくれたと言えます。
sync.Mutex
は、再入可能(re-entrant)ではありません。それについては、『プログラミング言語Go』(p,306)には次のように書かれています。Go のミューテックスが再入可能ではないことには正当な理由があります。ミューテックスの目的は、共有された変数のある種の不変式がプログラム実行中の重要な時点で維持されているのを保証することです。不変式の一つは、「共有された変数へアクセスするゴルーチンが存在しない」*2ですが、ミューテックスが保護しているデータ構造に特有の追加の不変式が存在するかもしれません。一つのゴルーチンがミューテックスロックを獲得したときには、そのゴルーチンは不変式が維持されていると想定するでしょう。ロックを保持している間に共有された変数が更新され、一時的に不変式が破られるかもしれません。しかし、ロックを解放するときには、秩序が回復されて不変式が再び維持されていることを保証しなければなりません。再入可能なミューテックスは他のゴルーチンが共有された変数へアクセスしていないことを保証するでしょうが、それらの変数の追加の不変式を保護することはできません。一方、Javaのオブジェクトのロック(モニター)は、再入可能です。つまり、同じスレッドが同じロックを複数回獲得できます。
*2 訳注:ゴルーチンがミューテックスロックを獲得する前の時点とそのロックを解放した時点で「共有された変数へアクセスするゴルーチンが存在しない」という意味です。この段落は、ミューテックスロックが獲得されていない場合の不変式について言及しています。
私自身がマルチスレッドプログラミングを本格的に行ったのは、Solaris 2.3を用いていたFuji Xerox DocuStation IM 200の開発です。当時、Solaris 2.3で提供されていたC言語用のスレッドライブラリ(POSIX Threadが登場する前です)は、再入可能なロックは提供していませんでした。そのためデッドロックが発生すると、場合によってはC++のメンバー関数の分割を行ったものです。privateのロックを獲得しないメンバー関数と、ロックを獲得してprivateのメンバー関数を呼ぶpublicの公開用のメンバー関数へと分割します。この分割については、上記の『プログラミング言語Go』にも書かれています。
IM 200が商品化された後の1996年にJavaを学び始めた時に、ロックが再入可能だというのは非常に魅力的に思えました。Javaの影響を受けて、2000年に設計したC++用のスレッドライブラリ(「clibと呼ばれるライブラリー開発の思い出」)では、再入可能なロックを提供する
CSynchronizer
というクラスを用意しました。そのライブラリを用いた最初の商品開発では、オブジェクトを介してスレッド間で同期するという設計は行われていませんでしたので、再入可能であることはそれほど利点がありませんでした。2003年から2009年に従事したコントローラソフトウェア開発では、clibを用いてマルチスレッドプログラミングをかなり行ったのですが、再入可能であるために起きる問題に悩まされて、マルチスレッドプログラミングの難しさを痛感したものです。幸い、その開発は完全なテスト駆動開発でしたので、「マルチスレッドプログラミングにおける重要な4要件」を満たしており、様々な問題を解決する経験を積むことができました。このソフトウェア開発で経験した問題は、『Effective Java 第3版』でJoshua Blochが述べていることそのものでした。
再入可能ロックは、マルチスレッドのオブジェクト指向プログラムの作成を単純化しますが、活性エラーを安全性エラーに変える可能性があります。
『Effective Java 第3版』(p.319)
活性エラーはデッドロックとして表面化しますが、安全性エラーはコードが動作しているように見えるが正しく動作していないという問題を引き起こします。特に競合状態(race condition)により引き起こされる問題は、現象の再現性も低く、調査が非常に難しいことが多いです。
2013年7月から2015年5月まで行ったコントローラソフトウェア開発は、私にとっては4回目のコントローラソフトウェア開発でしたが、マルチスレッドプログラミングの複雑さをGo言語のゴルーチンとチャネルで軽減できないかの検証も狙っていました。しかし、やはりデジタル複合機のコントローラソフトウェアは複雑であり、デッドロックはよく発生しましたし、競合状態も発生しました。幸い、Go言語には競合検出機能が提供されているので、その点は助かりました。
現在、従事しているソフトウェア開発では、再入可能性が問題となるような複雑なソフトウェア開発は行っていません。その点では、4回のデジタル複合機のコントローラソフトウェア開発は、私に貴重な経験を与えてくれたと言えます。
プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- 作者: Alan A.A. Donovan
- 出版社/メーカー: 丸善出版
- 発売日: 2016/06/20
- メディア: 単行本(ソフトカバー)
2018-10-23 07:05
コメント(0)
コメント 0