yohhoyの日記

技術的メモをしていきたい日記

(翻訳)C++トランザクシショナル言語構成要素のドラフト仕様 V1.1 [§1-2のみ]

元文書:Transactional Memory Specification Drafting Group, "Draft Specification of Transactional Language Constructs for C++"*1, Version 1.1, 2012/2/3

訳出メモ:

  • 次世代C++1y標準のトランザクショナルメモリに関するドラフト仕様を一部翻訳。[§1.Overview〜§2.Transaction statementのみ]
  • 訳文中ではトランザクション種別としての "relaxed", "atomic" は英語表記のままとする。
  • C++11実行モデルにおける "sequenced before", "synchronizes with", "happens before" は必ず英語表記を併記する。

gcc 4.7.0からトランザクショナルメモリの実験的サポートが始まった(-fgnu-tmオプション)。また、C++標準化に対しては(PDF)N3341 Transactional Language Constructs for C++が提出されている。

1. 概観/Overview

本仕様ではC++にトランザクシショナル言語構成要素を導入する。明示的な同期処理なしに他スレッドと相互作用がない複合文(compound statement)を記述可能とすることで、並行プログラミングを容易にすることを目指す。本仕様で導入される機能を下記に概説する。

本仕様はC++11仕様の上に基づく。従って、本仕様にて記述される構成要素はデータ競合のないプログラムにおいてのみwell-definedな振る舞いを持つ。本仕様はトランザクション構成要素がどのようにプログラムがデータ競合を含むか否かの決定に寄与するかについて規定する(§2.1)。

__transaction_relaxedキーワード(§3)は、relaxedトランザクションとして実行すべき複合文を示すために用いられる。relaxedトランザクションの実行中は他トランザクションによる変更を観測せず、かつ他トランザクションからはその完了前の部分結果を観測しない。relaxedトランザクションは任意の非トランザクションコードを含むことができ、それゆえ従来の同期方式との相互運用性を提供する。ただし、relaxedトランザクションは他スレッドの非トランザクション処理とはインターリーブされるかもしれない。

より厳密なトランザクション分離性を強制するため、__transaction_atomicキーワード(§4)によるatomicトランザクション表現を導入する。atomicトランザクションは単一の分割不能な文として実行する;すなわち、その実行中は他スレッドによる変更を観測せず、かつ他スレッドはその完了前の部分結果を観測しない。さらに、atomicトランザクションが実施される場合は、トランザクション全体が実施される。(訳注:トランザクションACID特性のAtomicityを意味する。)

追加された2つの文法要素は、relaxedまたはatomicトランザクションとして実行される式(§5)および関数(§6)指定に用いることもできる。

atomicトランザクションの振る舞いを実現するため、atomicトランザクションが“安全(safe)”な文(§4.2)のみを含むべきという制限をコンパイラは強制する。atomicトランザクションから呼ぶ関数もまた安全な文のみを含むべきであり、このような関数―および関数へのポインタ―は通常transaction_safe属性として宣言されるべきである*2。ただし特定の状況においては、このようなアノテートがされ無くとも関数は安全であると推論される(§3.2)。これはatomicトランザクション内で関数テンプレート使用を許容するという点で特に役立つ。関数がtransaction_safeと推論されるのを防ぐために、transaction_unsafe属性でアノテートしてもよい。これは、将来にわたって関数が安全であると言えないとき、atomicトランザクション内で関数が使用されるのを防ぐという点で役立つ。仮想関数に対する属性は、オーバーライドされる任意の基底クラス仮想関数に対する属性と互換性がなければならない(§10)。メンバ関数に対する属性指定の負担を最小化するため、クラス定義では全メンバ関数に対する既定の属性をアノテートすることができ、またこの既定値は別個に上書き可能でもある(§11)。

atomicトランザクション文は__transaction_cancel文を用いてキャンセル可能であり(§8)、その場合はいかなる効果も持たない。キャンセル操作により、エラーや例外発生時におけるatomicトランザクションの部分的な効果を取り消すクリーンアップコード記述が不要となる。cancel-and-throw文を構成するcancel文とthrow文の組み合わせにより、キャンセルされたトランザクション文から例外を送出できる(§9)。

atomicトランザクション入れ子構造とできる。また一方で、outer属性にて最外のatomicトランザクションとしてマークし、入れ子とされること禁止することもできる(§4.1)。cancelまたはcancel-and-throw文はouter属性でアノテート可能であり、最外のatomicトランザクションがキャンセルされることを示す(§8.1, §9.1)。このようなcancelおよびcancel-and-throw文は、outer属性とされたトランザクション文の動的な範囲内でのみ実行可能である。関数や関数ポインタに対するtransaction_may_cancel_outer属性は、本ルールのコンパイル時における強制を手助けする。(訳注:「動的な範囲」はプログラム実行時に決定する最外トランザクション文を指す。)

明示的キャンセルによらないatomicトランザクションからの例外送出が行われたとしても、atomicトランザクションはそのまま実施される。atomicトランザクション内部からの例外送出が生じえる(または生じえない)と明示するnoexcept指定(§7)を用いて、atomicトランザクションからの予期しない例外送出による厄介なバグから保護できる。noexcept指定により例外送出しないと明示されたatomicトランザクションのスコープ外へ例外送出された場合、プログラム終了を引き起こす実行時エラーが発生する。

Appendix Aでは新機能における文法を記載する。Appendix Bでは本文書が記述する機能や別途要望機能のサブセット実装を検討する実装者を助けるため、機能間の依存性について議論する。Appendix Cでは本仕様が提示した機能について幾つかの拡張候補を議論する。Appendix Dは前バージョン仕様からの差分を記載する。

2. トランザクション文/Transaction statement

__transaction_relaxedもしくは__transaction_atomicキーワードに続く複合文は、トランザクションとして実行されるトランザクション(transaction statement)を定義する:

__transaction_relaxed compound-statement
__transaction_atomic compound-statement

データ競合フリーなプログラム(§2.1)では、全てのトランザクションはある全順序(total order)による逐次的な実行として現れる。これはトランザクションは他トランザクションから分離実行されることを意味する;すなわち、トランザクションにおける各個の操作は別トランザクションにおける各個の操作とインターリーブされて現れない。

[ノート: トランザクションはある全順序で実行されたかのように(as if)振る舞うが、実装(すなわちコンパイラ、ランタイム、ハードウェア)には逐次順序と同じ効果をちつつトランザクションを並行に実行する自由度がある。]

__transaction_relaxedキーワードで定義されたトランザクション文は、relaxedトランザクションを規定する(§3)。__transaction_atomicキーワードで定義されたトランザクション文は、atomicトランザクションを規定する(§4)。relaxedトランザクションは内包する操作の種類を制限しないが、全てのトランザクションに対する基本的な分離性保証(isolation guarantee)しか提供しない―他のrelaxedおよびatomicトランザクションに対して逐次的な実行として現れる。relaxedトランザクションは他方のスレッドにおける非トランザクション操作とはインターリーブされて現れるかもしれない。atomicトランザクションは強い分離性保証を提供する;つまり、他スレッドのいかなる操作ともインターリーブされて現れない。ただし、atomicトランザクションは“安全”なコードのみを含むことができる(§4.2)。

goto文やswitch文を、トランザクション文の内側へ制御を移すために用いてはならない。goto, break, return, continue文は、トランザクション文の外側へ制御を移すために用いられるかもしれない。この場合、トランザクション文内で宣言された変数は直接宣言されたコンテキストにおいて破棄される。

トランザクション文の本体はその内部で処理されなかった例外を送出するかもしれず、この場合の例外はトランザクション文の外部へと伝搬する(§7)。

2.1 メモリモデル/Memory model

トランザクションはプログラムの実行(execution)に対する順序制約を課す。この点において、トランザクションC++11標準が定める同期メカニズム(ロックやatomic変数など)と類する同期操作として働く。C++11標準はマルチスレッドプログラムにおいてread操作からどの値が観測可能かと決定するルールを定義する。トランザクションは異なるスレッド上の操作間に追加の順序制約を導入するため、このルールに影響を与える。

C++11メモリモデルの概略:

プログラムの実行は、含まれる全てのスレッドの実行から構成される。各々のスレッドにおける操作は、各スレッドのシングルスレッド・セマンティクスと一致する“sequenced before”関係によって順序付けられる。C++11標準ライブラリは同期操作(synchronization operations)と特に識別される幾つかの操作を定義する。同期操作にはロック上の操作や一部のatomic操作(C++11 atomic変数上での操作)が挙げられる。さらに、同期操作ではないmemory_order_relaxed atomic操作も存在する。ある種の同期操作はもう一方のスレッドで行われた他の同期操作と同期する(synchronize with)。(例えば、同一ロック上でのロック解放は次のロック獲得と同期する(synchronize with))。

“sequenced before”関係と“synchronizes with”関係は“happens before”関係を導く。“happens before”関係は下記ルールにて定義される:

  1. 操作Aが操作Bより前に順序付けられる(A sequenced before B)ならば、AはBより前に発生(A happens before B)する。
  2. 操作Aが操作Bと同期する(A synchronizes with B)ならば、AはBより前に発生(A happens before B)する。
  3. 操作AがBより前に発生(A happens before B)する操作Bが存在し、かつBが操作Cより前に発生(B happens before C)するならば、AはCより前に発生(A happens before C)する。

memory_order_consume atomic操作の存在は、“happens before”関係の定義をより複雑なものにする。この場合の“happens before”関係はもはや推移的(transitive)ではない。ただこの追加的な複雑性は、本仕様とは直交しておりまた概略の範囲を超えるものである。)処理系は“happens before”関係が循環するようなプログラム実行を行わないと保証すべきである。

あるメモリ位置に対して一方が変更をおこない、同一メモリ位置に対して他方がアクセスまたは変更を行うとき、2つの操作は衝突(conflict)する。異なるスレッドにおいて2つの衝突する操作があり、少なくとも一方がatomic操作でないもしくは一方が他方よりも前に発生(happens before)しないとき、プログラムの実行はデータ競合(data race)を含む。いかなるデータ競合も未定義動作(undefined behavior)をもたらす。データ競合を含む実行がまったく存在しないならば、プログラムはデータ競合フリー(data-race-free)である。データ競合フリーなプログラムにおいて、非atomicなメモリ位置からの各々のread操作は、“happens before”関係により順序付けられる最後のwrite操作で書いた値を読み取る。既定値memory_order_seq_cst以外のatomic操作を用いないデータ競合フリーなプログラムの振る舞いは、その逐次一貫性実行のいずれかひとつに従う。]

最外トランザクション(つまり、動的に他トランザクション入れ子になっていないトランザクション)は、“synchronizes with”関係に寄与する、あるグローバルな全順序による逐次的な実行として現れる*3。概念的に、各々の最外トランザクショントランザクション開始と終了を示すStartTransactionおよびEndTransation操作に関連づけられる脚注1。StartTransaction操作は、トランザクション内の全ての他操作より前に順序付けられる(sequenced before)。トランザクション内の全ての操作は、そのEndTransaction操作より前に順序付けられる(sequenced before)。トランザクションTに対し、Tの一部でなくかつTのある操作より前に順序付け(sequenced before)られる任意の操作は、TのStartTransaction操作より前に順序付けられる(sequenced before)。トランザクションTに対し、TのEndTransaction操作は任意の操作Aより前に順序付けられる(sequenced before)。ここでAはTの一部でなくかつT内のある操作がAより前に順序付けられる(sequenced before)ものとする。*4
脚注1: これらの操作は単にトランザクションがどのように“synchronizes with”関係に寄与するかを記述する目的で導入した。

全てのStartTransactionおよびEndTransaction操作に渡って、“sequenced before”関係に一致するトランザクション順序(transaction order)と呼ばれる全順序が存在する。この順序ではトランザクションはインターリーブされない;すなわち、一方のスレッドによって実行される対をなすStartTransaction/EndTransaction操作の間に、他方のスレッドによってStartTransactionやEndTransaction操作が実行されることは無い。

トランザクション順序はC++11標準が定義する“synchronizes with”関係に寄与する。特に各EndTransaction操作は、異なるスレッドによってトランザクション順序における次のStartTransaction操作と同期する(synchronizes with)。

[ノート: “synchronizes with”関係の定義は、“happens before”関係やread操作がどの値を読むか規定する可視性ルールの定義、およびデータ競合フリーの定義を含むメモリモデル全域に影響を与える。それゆえ、“synchronizes with”関係へのトランザクション導入では、トランザクション文の説明に必要な変更のみをメモリモデルに加える。この拡張により、C++11メモリモデルはトランザクション文を含むプログラムの振る舞いを完全に記述する。]

[ノート: 共有メモリアクセスはトランザクション文内であってもデータ競合になりうる。次の例では、スレッドT2によるwrite操作はスレッドT1によるxへのread/write両操作とデータ競合を引き起こす。これは、スレッドT1の操作が“happens-before”関係にて順序付けされないため。この例でデータ競合を避けるには、スレッドT2によるxへのwrite操作をトランザクション文とすべきである。

スレッドT1 スレッドT2
__transaction_relaxed {
  t = x;
  x = t+1;
}
x = 1;

[ノート: C++11メモリモデルはコンパイラの最適化にとって重要である。同期操作(StartTransactionおよびEndTransaction操作を含む)間のコードのみを対象とした、データ競合を引き起こさないような逐次的な妥当ソース-to-ソース変換では妥当性を保持する。本仕様の特定実装系に依存するような、データ競合を引き起こす(トランザクションの外部へのload操作の移動など)コンパイラのソース-to-ソース変換は不正である。*5

*1:http://sites.google.com/site/tmforcplusplus/よりファイル C++TransactionalConstructs-1.1.pdf をダウンロード可能。

*2:訳注:C++11で新しく導入された属性(attribute)構文を利用する。transaction_safe属性であれば、コード上では [[transaction_safe]] と記述する。

*3:訳注:全ての最外トランザクション文が単一のロックで排他制御された状態と等価。ただし、これはあくまでトランザクション順序の概念説明であり、単一ロックによる排他制御では並行性が完全に失われてしまう。

*4:訳注:厳密な仕様記述のため非常に理解しづらい表現となっている。大雑把な表現では「Tより前の操作 ⇒ StartTransaction ⇒ T内の操作 ⇒ EndTransaction ⇒ Tより後の操作」という直観通りとなる。(ここで "⇒" はsequenced before関係)

*5:訳注:直訳文では意味を取りづらい。コンパイラの最適化としてのソースコードレベルでの変換作業を指す(と考えられる)。「本仕様に従う妥当なソースコードは妥当性を保って最適化せよ」と言っている(気がする)。