トランザクショナルメモリ (TM TS)
トランザクショナルメモリは、文のグループをトランザクションにまとめる並行性同期メカニズムであり、トランザクションは以下の性質を持ちます。
- 原子性 (アトミック): すべての文が実行されるか、何も実行されないかのいずれかである
- 分離性 (アイソレーション): トランザクション内の文は、たとえ並列に実行されたとしても、他のトランザクションによって行われた書き込み途中の内容を観測しない
典型的な実装では、サポートされている場合はハードウェアトランザクショナルメモリを、利用可能な範囲(例:変更セットが飽和するまで)で使用し、それ以外はソフトウェアトランザクショナルメモリにフォールバックします。ソフトウェアトランザクショナルメモリは通常、楽観的並行性制御で実装されます。つまり、あるトランザクションが使用する変数のいずれかを別のトランザクションが更新した場合、そのトランザクションは暗黙のうちに再試行されます。このため、再試行可能なトランザクション(「アトミックブロック」)はトランザクションセーフな関数しか呼び出せません。
トランザクション内とトランザクション外で、他の外部同期なしに変数にアクセスすることはデータ競合であることに注意してください。
機能テストマクロがサポートされている場合、ここで説明されている機能は、マクロ定数 __cpp_transactional_memory が 201505 以上の値を持つことで示されます。
目次 |
[編集] 同期ブロック (Synchronized blocks)
synchronized 複合文
複合文をグローバルロックの下にあるかのように実行します。つまり、プログラム内のすべての最も外側の同期ブロックは、単一の全順序で実行されます。各同期ブロックの終わりは、その順序における次の同期ブロックの始まりと同期します。他の同期ブロック内にネストされた同期ブロックには、特別なセマンティクスはありません。
同期ブロックは(後述のアトミックブロックとは異なり)トランザクションではなく、トランザクションアンセーフな関数を呼び出すことができます。
#include <iostream> #include <thread> #include <vector> int f() { static int i = 0; synchronized { // begin synchronized block std::cout << i << " -> "; ++i; // each call to f() obtains a unique value of i std::cout << i << '\n'; return i; // end synchronized block } } int main() { std::vector<std::thread> v(10); for (auto& t : v) t = std::thread([] { for (int n = 0; n < 10; ++n) f(); }); for (auto& t : v) t.join(); }
出力
0 -> 1 1 -> 2 2 -> 3 ... 99 -> 100
何らかの手段(ブロックの末尾に到達、goto、break、continue、returnの実行、または例外のスロー)で同期ブロックを抜けると、ブロックが外側のブロックであった場合、そのブロックを抜け、単一の全順序における次のブロックと同期します。std::longjmp を使用して同期ブロックを抜けた場合の動作は未定義です。
goto または switch によって同期ブロックに入ることは許可されません。
同期ブロックはグローバルロックの下にあるかのように実行されますが、実装は各ブロック内のコードを検査し、トランザクションセーフなコードには(利用可能であればハードウェアトランザクショナルメモリに支援された)楽観的並行性制御を、トランザクションセーフでないコードには最小限のロックを使用することが期待されます。同期ブロックがインライン化されていない関数を呼び出す場合、コンパイラは投機的実行を中断し、その関数が transaction_safe と宣言されているか(下記参照)、または属性 [[optimize_for_synchronized]](下記参照)が使用されていない限り、呼び出し全体をロックで囲む必要があるかもしれません。
[編集] アトミックブロック (Atomic blocks)
| このセクションは未完成です |
atomic_noexcept 複合文
atomic_cancel 複合文
atomic_commit 複合文
atomic_cancel ブロックでトランザクションのキャンセルに使用される例外は、std::bad_alloc、std::bad_array_new_length、std::bad_cast、std::bad_typeid、std::bad_exception、std::exception およびそこから派生したすべての標準ライブラリ例外、そして特別な例外型 std::tx_exception<T> です。
アトミックブロック内の 複合文 は、transaction_safe でない式や文を実行したり、関数を呼び出したりすることはできません(これはコンパイル時エラーになります)。
// each call to f() retrieves a unique value of i, even when done in parallel int f() { static int i = 0; atomic_noexcept { // begin transaction // printf("before %d\n", i); // error: cannot call a non transaction-safe function ++i; return i; // commit transaction } }
例外以外の手段(末尾への到達、goto、break、continue、return)でアトミックブロックを抜けると、トランザクションはコミットされます。std::longjmp を使用してアトミックブロックを抜けた場合の動作は未定義です。
[編集] トランザクションセーフな関数 (Transaction-safe functions)
| このセクションは未完成です |
関数は、その宣言にキーワード transaction_safe を使用することで、明示的にトランザクションセーフであると宣言できます。
| このセクションは未完成です |
ラムダ宣言では、キャプチャリストの直後、または(mutable キーワードが使用されている場合は)その直後に現れます。
| このセクションは未完成です |
extern volatile int * p = 0; struct S { virtual ~S(); }; int f() transaction_safe { int x = 0; // ok: not volatile p = &x; // ok: the pointer is not volatile int i = *p; // error: read through volatile glvalue S s; // error: invocation of unsafe destructor }
int f(int x) { // implicitly transaction-safe if (x <= 0) return 0; return x + f(x - 1); }
トランザクションセーフでない関数を、トランザクションセーフな関数への参照またはポインタを介して呼び出した場合、その動作は未定義です。
トランザクションセーフな関数へのポインタおよびトランザクションセーフなメンバ関数へのポインタは、それぞれ関数へのポインタおよびメンバ関数へのポインタに暗黙的に変換可能です。変換後のポインタが元のポインタと等しいかどうかは未規定です。
[編集] トランザクションセーフな仮想関数 (Transaction-safe virtual functions)
| このセクションは未完成です |
transaction_safe_dynamic な関数の最終オーバーライダが transaction_safe と宣言されていない場合、それをアトミックブロック内で呼び出すと、その動作は未定義です。
[編集] 標準ライブラリ (Standard library)
新しい例外テンプレート std::tx_exception の導入に加えて、トランザクショナルメモリ技術仕様は標準ライブラリに以下の変更を加えます。
- 以下の関数を明示的に
transaction_safeにします。
- std::forward、std::move、std::move_if_noexcept、std::align、std::abort、グローバルデフォルトの operator new、グローバルデフォルトの operator delete、呼び出されるコンストラクタがトランザクションセーフな場合の std::allocator::construct、呼び出されるデストラクタがトランザクションセーフな場合の std::allocator::destroy、std::get_temporary_buffer、std::return_temporary_buffer、std::addressof、std::pointer_traits::pointer_to、トランザクションのキャンセルをサポートするすべての例外型(上記の
atomic_cancel参照)の各非仮想メンバ関数このセクションは未完成です
理由:他にもあります
- std::forward、std::move、std::move_if_noexcept、std::align、std::abort、グローバルデフォルトの operator new、グローバルデフォルトの operator delete、呼び出されるコンストラクタがトランザクションセーフな場合の std::allocator::construct、呼び出されるデストラクタがトランザクションセーフな場合の std::allocator::destroy、std::get_temporary_buffer、std::return_temporary_buffer、std::addressof、std::pointer_traits::pointer_to、トランザクションのキャンセルをサポートするすべての例外型(上記の
- 以下の関数を明示的に
transaction_safe_dynamicにします。
- トランザクションのキャンセルをサポートするすべての例外型(上記の
atomic_cancel参照)の各仮想メンバ関数
- トランザクションのキャンセルをサポートするすべての例外型(上記の
- Allocator X 上でトランザクションセーフなすべての操作が、
X::rebind<>::other上でもトランザクションセーフであることを要求します。
[編集] 属性 (Attributes)
属性 [[optimize_for_synchronized]] は、関数宣言の宣言子に適用することができ、その関数の最初の宣言に現れなければなりません。
ある翻訳単位で関数が [[optimize_for_synchronized]] と宣言され、別の翻訳単位で同じ関数が [[optimize_for_synchronized]] なしで宣言された場合、そのプログラムは不適格 (ill-formed) ですが、診断は要求されません。
これは、その関数定義が synchronized 文からの呼び出しに対して最適化されるべきであることを示します。特に、呼び出しの大部分ではトランザクションセーフだが、すべての呼び出しでそうとは限らない(例:リハッシュが必要になる可能性のあるハッシュテーブルの挿入、新しいブロックを要求する必要があるかもしれないアロケータ、稀にログを記録する単純な関数など)関数を呼び出す同期ブロックがシリアライズされるのを避けます。
std::atomic<bool> rehash{false}; // maintenance thread runs this loop void maintenance_thread(void*) { while (!shutdown) { synchronized { if (rehash) { hash.rehash(); rehash = false; } } } } // worker threads execute hundreds of thousands of calls to this function // every second. Calls to insert_key() from synchronized blocks in other // translation units will cause those blocks to serialize, unless insert_key() // is marked [[optimize_for_synchronized]] [[optimize_for_synchronized]] void insert_key(char* key, char* value) { bool concern = hash.insert(key, value); if (concern) rehash = true; }
属性なしのGCCアセンブリ:関数全体がシリアライズされます
insert_key(char*, char*): subq $8, %rsp movq %rsi, %rdx movq %rdi, %rsi movl $hash, %edi call Hash::insert(char*, char*) testb %al, %al je .L20 movb $1, rehash(%rip) mfence .L20: addq $8, %rsp ret
属性ありのGCCアセンブリ
transaction clone for insert_key(char*, char*): subq $8, %rsp movq %rsi, %rdx movq %rdi, %rsi movl $hash, %edi call transaction clone for Hash::insert(char*, char*) testb %al, %al je .L27 xorl %edi, %edi call _ITM_changeTransactionMode # Note: this is the serialization point movb $1, rehash(%rip) mfence .L27: addq $8, %rsp ret
| このセクションは未完成です 理由:trunkでアセンブリを確認し、呼び出し側での変更も示す |
[編集] ノート (Notes)
| このセクションは未完成です 理由:Wyattの論文/講演からの経験談 |
[編集] キーワード (Keywords)
atomic_cancel, atomic_commit, atomic_noexcept, synchronized, transaction_safe, transaction_safe_dynamic
[編集] コンパイラサポート (Compiler support)
この技術仕様は、GCC 6.1 以降でサポートされています(有効にするには -fgnu-tm が必要です)。この仕様の古い亜種は、GCC 4.7 以降でサポートされていました。