命名空间
变体
操作

事务性内存 (TM TS)

来自 cppreference.com
< cpp‎ | language
 
 
C++ 语言
表达式
备用表示
字面量
布尔值 - 整数 - 浮点型
字符 - 字符串 - nullptr (C++11)
用户定义 (C++11)
实用程序
属性 (C++11)
类型
typedef 声明
类型别名声明 (C++11)
强制转换
内存分配
特定于类的函数属性
explicit (C++11)
static

特殊成员函数
模板
其他
 
 

事务性内存是一种并发同步机制,它将一组语句组合成事务,这些事务是

  • 原子(所有语句都执行,或者都不执行)
  • 隔离(事务中的语句可能不会观察到另一个事务所做的半写入,即使它们并行执行)

典型实现使用硬件事务性内存(如果支持的话)以及其可用的范围(例如,直到更改集饱和)并回退到软件事务性内存,通常使用乐观并发实现:如果另一个事务更新了事务使用的某些变量,则会静默地重试。因此,可重试事务(“原子块”)只能调用事务安全函数。

请注意,在没有其他外部同步的情况下,在事务中和事务外访问变量会发生数据竞争。

如果支持特性测试,则此处描述的特性由宏常量 __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 的表达式或语句或调用任何函数(这是一个编译时错误)。

// 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_cancelatomic_commitatomic_noexceptsynchronizedtransaction_safetransaction_safe_dynamic

[编辑] 编译器支持

从 GCC 6.1 版本开始支持此技术规范(需要 -fgnu-tm 来启用)。此规范的较早变体在 GCC 4.7 中 受支持