Namespaces
Variants

Transactional memory (TM TS)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

トランザクショナルメモリは、並行性同期メカニズムであり、トランザクション内で文のグループを結合するもので、

  • 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 複合文

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

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 とします:
  • 以下の関数を明示的に 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

注記

キーワード

atomic_cancel , atomic_commit , atomic_noexcept , synchronized , transaction_safe , transaction_safe_dynamic

コンパイラサポート

この技術仕様はGCCバージョン6.1以降でサポートされています(有効にするには - fgnu - tm が必要です)。この仕様の旧バリアントは GCC 4.7以降でサポートされていました