事务内存 (TM TS)
事务内存是一种并发同步机制,它将语句组组合在事务中,这些事务是
- 原子性 (要么所有语句都发生,要么什么都不发生)
- 隔离性 (即使事务并行执行,事务中的语句也可能无法观察到另一个事务进行到一半的写入)
典型的实现方式是在支持硬件事务内存的情况下使用硬件事务内存,并在可用性允许的范围内使用 (例如,直到变更集饱和),然后回退到软件事务内存,软件事务内存通常使用乐观并发实现:如果另一个事务更新了某个事务使用的某些变量,则会静默重试。因此,可重试事务 (“原子块”) 只能调用事务安全函数。
请注意,在事务内外访问变量且没有其他外部同步是数据竞争。
如果支持特性测试,此处描述的特性由宏常量 __cpp_transactional_memory 指示,其值等于或大于 201505。
目录 |
[编辑] 同步块
synchronized
compound-statement
执行复合语句,如同在全局锁下一样:程序中所有最外层的同步块都以单一的总顺序执行。每个同步块的结束都与该顺序中下一个同步块的开始同步。嵌套在其他同步块内的同步块没有特殊的语义。
同步块不是事务 (与下面的原子块不同),并且可以调用事务不安全函数。
#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
compound-statement
atomic_cancel
compound-statement
atomic_commit
compound-statement
在 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
的表达式、语句或调用任何非 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
(如果使用了 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
本节尚不完整 原因:检查主干版本的汇编,并显示调用方的更改 |
[编辑] 注释
本节尚不完整 原因:来自 Wyatt 论文/演讲的经验注释 |
[编辑] 关键字
atomic_cancel, atomic_commit, atomic_noexcept, synchronized, transaction_safe, transaction_safe_dynamic
[编辑] 编译器支持
此技术规范自 GCC 6.1 版本起受支持 (需要 -fgnu-tm 才能启用)。 此规范的旧版本自 GCC 4.7 起受支持。