名前空間
変種
操作

トランザクショナルメモリ (TM TS)

From cppreference.com
< cpp‎ | language
 
 
C++言語
全般
フロー制御
条件実行文
if
繰り返し文 (ループ)
for
範囲for (C++11)
ジャンプ文
関数
関数宣言
ラムダ式
inline指定子
動的例外仕様 (C++17まで*)
noexcept指定子 (C++11)
例外
名前空間
指定子
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点数
文字 - 文字列 - nullptr (C++11)
ユーザー定義 (C++11)
ユーティリティ
属性 (C++11)
typedef宣言
型エイリアス宣言 (C++11)
キャスト
メモリ確保
クラス
クラス固有の関数プロパティ
explicit (C++11)
static

特殊メンバ関数
テンプレート
その他
 
 

トランザクショナルメモリは、文のグループをトランザクションにまとめる並行性同期メカニズムであり、トランザクションは以下の性質を持ちます。

  • 原子性 (アトミック): すべての文が実行されるか、何も実行されないかのいずれかである
  • 分離性 (アイソレーション): トランザクション内の文は、たとえ並列に実行されたとしても、他のトランザクションによって行われた書き込み途中の内容を観測しない

典型的な実装では、サポートされている場合はハードウェアトランザクショナルメモリを、利用可能な範囲(例:変更セットが飽和するまで)で使用し、それ以外はソフトウェアトランザクショナルメモリにフォールバックします。ソフトウェアトランザクショナルメモリは通常、楽観的並行性制御で実装されます。つまり、あるトランザクションが使用する変数のいずれかを別のトランザクションが更新した場合、そのトランザクションは暗黙のうちに再試行されます。このため、再試行可能なトランザクション(「アトミックブロック」)はトランザクションセーフな関数しか呼び出せません。

トランザクション内とトランザクション外で、他の外部同期なしに変数にアクセスすることはデータ競合であることに注意してください。

機能テストマクロがサポートされている場合、ここで説明されている機能は、マクロ定数 __cpp_transactional_memory201505 以上の値を持つことで示されます。

目次

[編集] 同期ブロック (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 複合文

1) 例外がスローされた場合、std::abort が呼び出されます。
2) 例外がスローされた場合、std::abort が呼び出されます。ただし、その例外がトランザクションのキャンセルに使用される例外(下記参照)の一つである場合は、トランザクションはキャンセルされます。つまり、アトミックブロックの操作の副作用によって変更されたプログラム内のすべてのメモリ位置の値が、アトミックブロックの開始時に持っていた値に復元され、例外は通常通りスタックの巻き戻しを続行します。
3) 例外がスローされた場合、トランザクションは通常通りコミットされます。

atomic_cancel ブロックでトランザクションのキャンセルに使用される例外は、std::bad_allocstd::bad_array_new_lengthstd::bad_caststd::bad_typeidstd::bad_exceptionstd::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 にします。
  • 以下の関数を明示的に 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

[編集] ノート (Notes)

[編集] キーワード (Keywords)

atomic_cancel, atomic_commit, atomic_noexcept, synchronized, transaction_safe, transaction_safe_dynamic

[編集] コンパイラサポート (Compiler support)

この技術仕様は、GCC 6.1 以降でサポートされています(有効にするには -fgnu-tm が必要です)。この仕様の古い亜種は、GCC 4.7 以降でサポートされていました

English 日本語 中文(简体) 中文(繁體)