事務性記憶體 (TM TS)
事務性記憶體是一種併發同步機制,它將語句組合併到事務中,這些事務具有以下特性:
- 原子性(所有語句要麼全部發生,要麼全部不發生)
- 隔離性(事務中的語句不能觀察到另一個事務的半寫入,即使它們並行執行)
典型的實現會在支援硬體事務性記憶體的範圍內(例如,直到變更集飽和)使用它,並回退到軟體事務性記憶體,通常透過樂觀併發實現:如果另一個事務更新了事務使用的一些變數,則會靜默重試。因此,可重試事務(“原子塊”)只能呼叫事務安全函式。
請注意,在事務內部和外部訪問變數而沒有其他外部同步會導致資料競爭。
如果支援特性測試,此處描述的特性由宏常量 __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
複合語句
在 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
的表示式、語句或呼叫任何非 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
- 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
上也是事務安全的。
[編輯] 屬性
屬性 [[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
本節不完整 原因:檢查 trunk 中的彙編,並顯示呼叫者側的更改 |
[編輯] 註釋
本節不完整 原因:Wyatt 論文/演講中的經驗筆記 |
[編輯] 關鍵字
atomic_cancel, atomic_commit, atomic_noexcept, synchronized, transaction_safe, transaction_safe_dynamic
[編輯] 編譯器支援
此技術規範自 GCC 6.1 版起受支援(需要啟用 -fgnu-tm)。此規範的舊版本自 GCC 4.7 起 受支援。