名稱空間
變體
操作

事務性記憶體 (TM TS)

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
 
 

事務性記憶體是一種併發同步機制,它將語句組合併到事務中,這些事務具有以下特性:

  • 原子性(所有語句要麼全部發生,要麼全部不發生)
  • 隔離性(事務中的語句不能觀察到另一個事務的半寫入,即使它們並行執行)

典型的實現會在支援硬體事務性記憶體的範圍內(例如,直到變更集飽和)使用它,並回退到軟體事務性記憶體,通常透過樂觀併發實現:如果另一個事務更新了事務使用的一些變數,則會靜默重試。因此,可重試事務(“原子塊”)只能呼叫事務安全函式。

請注意,在事務內部和外部訪問變數而沒有其他外部同步會導致資料競爭。

如果支援特性測試,此處描述的特性由宏常量 __cpp_transactional_memory 表示,其值等於或大於 201505

目錄

[編輯] 同步塊

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_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 的表示式、語句或呼叫任何非 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 顯式宣告為事務安全。

lambda 宣告中,它出現在捕獲列表之後,或緊隨(關鍵字 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_dynamic 函式的最終覆蓋器未宣告為 transaction_safe,則在原子塊中呼叫它是未定義行為。

[編輯] 標準庫

除了引入新的異常模板 std::tx_exception 外,事務性記憶體技術規範對標準庫進行了以下更改:

  • 使以下函式顯式地為 transaction_safe
  • 使以下函式顯式地為 transaction_safe_dynamic
  • 所有支援事務取消的異常型別(見上述 atomic_cancel)的每個虛成員函式
  • 要求在 Allocator X 上事務安全的所有操作在 X::rebind<>::other 上也是事務安全的。

[編輯] 屬性

屬性 [[optimize_for_synchronized]] 可以應用於函式宣告中的宣告符,並且必須出現在函式的第一個宣告上。

如果在某個翻譯單元中函式宣告為 [[optimize_for_synchronized]],而在另一個翻譯單元中函式未宣告為 [[optimize_for_synchronized]],則程式格式錯誤;無需診斷。

它表明函式定義應該針對從 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

[編輯] 註釋

[編輯] 關鍵字

atomic_cancel, atomic_commit, atomic_noexcept, synchronized, transaction_safe, transaction_safe_dynamic

[編輯] 編譯器支援

此技術規範自 GCC 6.1 版起受支援(需要啟用 -fgnu-tm)。此規範的舊版本自 GCC 4.7 起 受支援