Transactional memory (TM TS)
トランザクショナルメモリは、並行性同期メカニズムであり、トランザクション内で文のグループを結合するもので、
- atomic(すべてのステートメントが実行されるか、何も実行されないかのいずれか)
- isolated(トランザクション内のステートメントは、並列実行された場合でも、他のトランザクションによる部分的に書き込まれた内容を観測することはない)
典型的な実装では、サポートされているハードウェアトランザクショナルメモリを利用可能な限り使用し(例:チェンジセットが飽和するまで)、ソフトウェアトランザクショナルメモリにフォールバックします。通常は楽観的並行性制御で実装され、別のトランザクションが対象変数を更新した場合、暗黙的に再試行されます。このため、再試行可能なトランザクション(「アトミックブロック」)はトランザクションセーフな関数のみを呼び出せます。
トランザクション内とトランザクション外で他の外部同期なしに変数にアクセスすることはデータ競合であることに注意してください。
機能テストがサポートされている場合、ここで説明されている機能は、マクロ定数 __cpp_transactional_memory によって示され、その値は 201505 以上となります。
目次 |
同期ブロック
synchronized
複合文
グローバルロック下にあるかのように compound statement を実行します:プログラム内のすべての最外層のsynchronizedブロックは単一の全順序で実行されます。各synchronizedブロックの終了は、その順序における次のsynchronizedブロックの開始と同期します。他のsynchronizedブロック内にネストされたsynchronizedブロックには特別なセマンティクスはありません。
同期ブロックはトランザクションではありません(以下のアトミックブロックとは異なります)。トランザクションセーフでない関数を呼び出す可能性があります。
#include <iostream> #include <thread> #include <vector> int f() { static int i = 0; synchronized { // 同期ブロックの開始 std::cout << i << " -> "; ++i; // f()の各呼び出しは一意なiの値を取得する std::cout << i << '\n'; return i; // 同期ブロックの終了 } } 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の実行、あるいは例外の送出)によって抜ける場合、そのブロックを終了し、抜けたブロックが外側のブロックであった場合は単一全順序における次のブロックと同期します(synchronizes-with)。 std::longjmp を使用して同期ブロックを抜けた場合、動作は未定義です。
gotoまたはswitchによる同期ブロックへの進入は許可されていません。
同期ブロックはグローバルロックの下で実行されるかのように動作しますが、実装では各ブロック内のコードを検査し、トランザクションセーフなコードに対しては楽観的並行性(利用可能な場合はハードウェアトランザクショナルメモリでバックアップ)を、非トランザクションセーフなコードに対しては最小限のロックを使用することが期待されています。同期ブロックが非インライン関数を呼び出す場合、コンパイラは投機的実行を中断し、その関数呼び出し全体をロックで囲む必要があるかもしれません。ただし、関数が
transaction_safe
(下記参照)として宣言されている場合、または
[[optimize_for_synchronized]]
属性(下記参照)が使用されている場合は除きます。
アトミックブロック
| このセクションは不完全です |
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>
です。
アトミックブロック内の
compound-statement
は、
transaction_safe
でない任意の式や文の実行、または任意の関数の呼び出しを行うことは許可されていません(これはコンパイル時エラーとなります)。
// f()の各呼び出しは、並列実行された場合でも一意のiの値を取得する int f() { static int i = 0; atomic_noexcept { // トランザクション開始 // printf("before %d\n", i); // エラー: トランザクションセーフでない関数を呼び出せない ++i; return i; // トランザクションコミット } }
例外以外の手段(終端への到達、goto、break、continue、return)によるアトミックブロックの終了は、トランザクションをコミットします。 std::longjmp を使用してアトミックブロックを終了した場合の動作は未定義です。
トランザクションセーフ関数
| このセクションは不完全です |
関数は、その宣言内でキーワード transaction_safe を使用することで明示的にトランザクションセーフであると宣言できます。
| このセクションは不完全です |
lambda宣言では、キャプチャリストの直後、または(使用されている場合)
mutable
キーワードの直後に現れます。
| このセクションは不完全です |
extern volatile int * p = 0; struct S { virtual ~S(); }; int f() transaction_safe { int x = 0; // OK: volatileではない p = &x; // OK: ポインタはvolatileではない int i = *p; // エラー: volatile glvalueを通じた読み込み S s; // エラー: 安全でないデストラクタの呼び出し }
int f(int x) { // 暗黙的にトランザクションセーフ if (x <= 0) return 0; return x + f(x - 1); }
トランザクションセーフでない関数が、トランザクションセーフ関数への参照またはポインタを通じて呼び出された場合、動作は未定義です。
トランザクションセーフ関数へのポインタおよびトランザクションセーフメンバ関数へのポインタは、それぞれ関数へのポインタおよびメンバ関数へのポインタに暗黙的に変換可能です。結果のポインタが元のポインタと等価と比較されるかどうかは未規定です。
トランザクションセーフ仮想関数
| このセクションは不完全です |
transaction_safe_dynamic
関数の最終オーバーライドが
transaction_safe
として宣言されていない場合、
アトミックブロック内でそれを呼び出すと未定義動作となります。
標準ライブラリ
新しい例外テンプレート 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::rebind<>::otherでもトランザクションセーフであることを要求する
属性
[[
optimize_for_synchronized
]]
属性は関数宣言の宣言子に適用でき、関数の最初の宣言に現れなければなりません。
関数がある翻訳単位で
[[optimize_for_synchronized]]
を付けて宣言され、同じ関数が別の翻訳単位で
[[optimize_for_synchronized]]
なしで宣言された場合、プログラムは不適格となる。診断は要求されない。
この関数定義は、 synchronized 文からの呼び出しに対して最適化されるべきであることを示します。特に、大多数の呼び出しではトランザクションセーフであるが、すべての呼び出しでそうとは限らない関数(例:リハッシュが必要になる可能性があるハッシュテーブルの挿入、新しいブロックを要求する可能性があるアロケータ、稀にログを出力する単純な関数)に対する呼び出しを行う同期化ブロックの直列化を回避します。
std::atomic<bool> rehash{false}; // メンテナンススレッドはこのループを実行する void maintenance_thread(void*) { while (!shutdown) { synchronized { if (rehash) { hash.rehash(); rehash = false; } } } } // ワーカースレッドはこの関数を毎秒数十万回呼び出す // 他の翻訳単位のsynchronizedブロックからのinsert_key()呼び出しは、 // insert_key()が[[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 # 注: これはシリアライゼーションポイントです movb $1, rehash(%rip) mfence .L27: addq $8, %rsp ret
|
このセクションは不完全です
理由: trunkでアセンブリを確認し、呼び出し側の変更も表示する |
注記
|
このセクションは不完全です
理由: Wyatt論文/講演からの実践ノート |
キーワード
atomic_cancel , atomic_commit , atomic_noexcept , synchronized , transaction_safe , transaction_safe_dynamic
コンパイラサポート
この技術仕様はGCCバージョン6.1以降でサポートされています(有効にするには - fgnu - tm が必要です)。この仕様の旧バリアントは GCC 4.7以降でサポートされていました 。