0%

C++11中的atomic

第1节和第2节简单介绍g++ (GCC) 4.8.5的atomic实现。

GCC的c++11 atomic 实现简介 (1)

atomic_flag (1.1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
struct __atomic_flag_base
{
__atomic_flag_data_type _M_i;
};

#define ATOMIC_FLAG_INIT { 0 }

struct atomic_flag : public __atomic_flag_base
{
atomic_flag() noexcept = default;
~atomic_flag() noexcept = default;
atomic_flag(const atomic_flag&) = delete;
atomic_flag& operator=(const atomic_flag&) = delete;
atomic_flag& operator=(const atomic_flag&) volatile = delete;

// Conversion to ATOMIC_FLAG_INIT.
constexpr atomic_flag(bool __i) noexcept
: __atomic_flag_base{ _S_init(__i) }
{ }

bool
test_and_set(memory_order __m = memory_order_seq_cst) noexcept
{
return __atomic_test_and_set (&_M_i, __m);
}

bool
test_and_set(memory_order __m = memory_order_seq_cst) volatile noexcept
{
return __atomic_test_and_set (&_M_i, __m);
}

void
clear(memory_order __m = memory_order_seq_cst) noexcept
{
memory_order __b = __m & __memory_order_mask;
__glibcxx_assert(__b != memory_order_consume);
__glibcxx_assert(__b != memory_order_acquire);
__glibcxx_assert(__b != memory_order_acq_rel);

__atomic_clear (&_M_i, __m);
}

void
clear(memory_order __m = memory_order_seq_cst) volatile noexcept
{
memory_order __b = __m & __memory_order_mask;
__glibcxx_assert(__b != memory_order_consume);
__glibcxx_assert(__b != memory_order_acquire);
__glibcxx_assert(__b != memory_order_acq_rel);

__atomic_clear (&_M_i, __m);
}

private:
static constexpr __atomic_flag_data_type
_S_init(bool __i)
{ return __i ? __GCC_ATOMIC_TEST_AND_SET_TRUEVAL : 0; }
};

atomic_flag和下文讲的其他atomic类型不同,它只支持test_and_setclear操作;默认构造函数构造的变量处于uninitialized状态(既没有被set也没有被clear);atomic_flag f = ATOMIC_FLAG_INIT构造的变量处于clear状态;

atomic_flag的内部是一个__atomic_flag_data_type _M_i__atomic_flag_data_type是一个bool或者unsigned char

1
2
3
4
5
#if __GCC_ATOMIC_TEST_AND_SET_TRUEVAL == 1
typedef bool __atomic_flag_data_type;
#else
typedef unsigned char __atomic_flag_data_type;
#endif

test_and_setclear操作是通过GCC的__atomic_test_and_set__atomic_clear(见第2节),并基于这个_M_i实现的。

__atomic_base (1.2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<typename _ITp>
struct __atomic_base
{
private:
typedef _ITp __int_type;

__int_type _M_i;

public:
__atomic_base() noexcept = default;
~__atomic_base() noexcept = default;
__atomic_base(const __atomic_base&) = delete;
__atomic_base& operator=(const __atomic_base&) = delete;
__atomic_base& operator=(const __atomic_base&) volatile = delete;

// Requires __int_type convertible to _M_i.
constexpr __atomic_base(__int_type __i) noexcept : _M_i (__i) { }
...
};

这个模板是为基础类型提供的,基础类型包括以下(不包括float, double):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
char
signed char
unsigned char
short
unsigned short
int
unsigned int
long
unsigned long
long long
unsigned long long
char16_t
char32_t
wchar_t

所以,atomic_T都是__atomic_base<T>的别名(T是基础类型,bool类型除外):

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef __atomic_base<char>                      atomic_char;
typedef __atomic_base<signed char> atomic_schar;
typedef __atomic_base<unsigned char> atomic_uchar;
typedef __atomic_base<short> atomic_short;
typedef __atomic_base<unsigned short> atomic_ushort;
typedef __atomic_base<int> atomic_int;
typedef __atomic_base<unsigned int> atomic_uint;
typedef __atomic_base<long> atomic_long;
typedef __atomic_base<unsigned long> atomic_ulong;
typedef __atomic_base<long long> atomic_llong;
typedef __atomic_base<unsigned long long> atomic_ullong;
typedef __atomic_base<wchar_t> atomic_wchar_t;
...

这些基础类型都属于某种整数,支持:

  • 加减运算++, --, +=, -=, fetch_add, fetch_sub
  • 位运算&=, |=^=, fetch_and, fetch_or, fetch_xor操作。
  • atomic通用(atomic_flag除外)操作:load, store, exchange, compare_exchange_weak, compare_exchange_strongis_lock_free

这些函数的实现都是基于GCC的__atomic函数实现的(见第2节)。另外,bool不支持加减运算和位运算,所以atomic_bool不是__atomic_base<bool>的别名(见第1.3节):

atomic_bool (1.3)

bool不支持数学运算和位运算,所以,atomic_bool不是__atomic_base<bool>的别名,而是通过单独的class atomic_bool实现的,只不过atomic_bool组合(而不是继承)一个__atomic_base<bool> _M_base,并借助这个_M_base实现bool支持的操作(atomic_bool的函数f()调用_M_basef()),摒弃了不支持的操作;这也是通过组合而不是继承来实现的原因:继承的话,atomic_bool就继承数学运算和位运算了。所以,atomic_bool只支持atomic通用(atomic_flag除外)操作:load, store, exchange, compare_exchange_weak, compare_exchange_strongis_lock_free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct atomic_bool
{
private:
__atomic_base<bool> _M_base;

public:
atomic_bool() noexcept = default;
~atomic_bool() noexcept = default;
atomic_bool(const atomic_bool&) = delete;
atomic_bool& operator=(const atomic_bool&) = delete;
atomic_bool& operator=(const atomic_bool&) volatile = delete;

constexpr atomic_bool(bool __i) noexcept : _M_base(__i) { }
...
};

__atomic_base<_PTp*> (1.4)

针对指针类型,对于__atomic_base(见第1.2节)的偏特化。例如operator++(),对于上述基础类型来说,是数学上的加1;而对于指针类型,是加_M_type_size(1),即1*sizeof(_PTp);并且,特化时,摒弃了位运算。

atomic (1.5)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<typename _Tp>
struct atomic
{
private:
_Tp _M_i;

public:
atomic() noexcept = default;
~atomic() noexcept = default;
atomic(const atomic&) = delete;
atomic& operator=(const atomic&) = delete;
atomic& operator=(const atomic&) volatile = delete;

constexpr atomic(_Tp __i) noexcept : _M_i(__i) { }
...
};

这个类不是基于__atomic_base实现的,是一个全新的实现,主要用于:

  • 浮点类型,自定义类型等;例如class Foo {...}; atomic<Foo>;
  • 特化以支持基础类型(见1.6-1.8节);

只支持atomic通用(atomic_flag除外)操作:load, store, exchange, compare_exchange_weak, compare_exchange_strongis_lock_free

atomic<_Tp*> (1.6)

针对指针类型,对于atomic(见第1.5节)的偏特化;atomic<_Tp*>组合一个__atomic_base<_Tp*> _M_b。特化时,在atomic上添加了++, --, +=, -=等操作,因为指针支持这些操作,实现上是调用_M_b的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<typename _Tp>
struct atomic<_Tp*>
{
typedef _Tp* __pointer_type;
typedef __atomic_base<_Tp*> __base_type;
__base_type _M_b;

atomic() noexcept = default;
~atomic() noexcept = default;
atomic(const atomic&) = delete;
atomic& operator=(const atomic&) = delete;
atomic& operator=(const atomic&) volatile = delete;

constexpr atomic(__pointer_type __p) noexcept : _M_b(__p) { }
...
};

template<> struct atomic<bool> : public atomic_bool (1.7)

针对bool类型的特化;是继承atomic_bool实现的;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/// Explicit specialization for bool.
template<>
struct atomic<bool> : public atomic_bool
{
typedef bool __integral_type;
typedef atomic_bool __base_type;

atomic() noexcept = default;
~atomic() noexcept = default;
atomic(const atomic&) = delete;
atomic& operator=(const atomic&) = delete;
atomic& operator=(const atomic&) volatile = delete;

constexpr atomic(__integral_type __i) noexcept : __base_type(__i) { }

using __base_type::operator __integral_type;
using __base_type::operator=;
};

template<> struct atomic<char> : public atomic_char (1.8)

针对bool类型的特化;是继承atomic_char实现的;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/// Explicit specialization for char.
template<>
struct atomic<char> : public atomic_char
{
typedef char __integral_type;
typedef atomic_char __base_type;

atomic() noexcept = default;
~atomic() noexcept = default;
atomic(const atomic&) = delete;
atomic& operator=(const atomic&) = delete;
atomic& operator=(const atomic&) volatile = delete;

constexpr atomic(__integral_type __i) noexcept : __base_type(__i) { }

using __base_type::operator __integral_type;
using __base_type::operator=;
};

关系图与分类 (1.9)

unsigned char, short, int, long, long long … 等都和char一样,不一一列出。最后以一张图表示它们的关系:

程序员应该使用绿色部分的类(模板)。atomic_flag相当于一个自旋锁;atomic<bool>, atomic<char>, atomic<short>, atomic<int>等是基础类型的atomic版;atomic<_Tp*>是指针类型的atomic版;atomic<Foo>是自定义类型的atomic版。所以,可以把atomic类型分为以下几类:

  • atomic_flag : 自旋锁
  • atomic<BaseType> : char, short, int, long, long long, unsigned char, unsigned int, …
  • atomic<bool> : bool
  • atomic<T*> : 指针
  • atomic<T> : 浮点,自定义类型

支持的操作 (1.10)

基于第1.9节的分类,它们支持的操作如下:

Operation atomic_flag atomic<BaseType> atomic<bool> atomic<T*> atomic<T>
test_and_set,clear Yes
++,--,+=,-=,fetch_add/sub Yes Yes
&=, |=, ^=,fetch_and/or/xor Yes
is_lock_free Yes Yes Yes Yes
load,store Yes Yes Yes Yes
exchange,compare_exchange_weak/strong Yes Yes Yes Yes

GCC的__atomic函数族 (2)

__atomic函数族包含的函数 (2.1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
type __atomic_load_n (type *ptr, int memmodel);
void __atomic_load (type *ptr, type *ret, int memmodel);
void __atomic_store_n (type *ptr, type val, int memmodel);
void __atomic_store (type *ptr, type *val, int memmodel);
type __atomic_exchange_n (type *ptr, type val, int memmodel);
void __atomic_exchange (type *ptr, type *val, type *ret, int memmodel);
bool __atomic_compare_exchange_n (type *ptr, type *expected, type desired, bool weak, int success_memmodel, int failure_memmodel);
bool __atomic_compare_exchange (type *ptr, type *expected, type *desired, bool weak, int success_memmodel, int failure_memmodel);

type __atomic_add_fetch (type *ptr, type val, int memmodel);
type __atomic_sub_fetch (type *ptr, type val, int memmodel);
type __atomic_and_fetch (type *ptr, type val, int memmodel);
type __atomic_xor_fetch (type *ptr, type val, int memmodel);
type __atomic_or_fetch (type *ptr, type val, int memmodel);
type __atomic_nand_fetch (type *ptr, type val, int memmodel);

type __atomic_fetch_add (type *ptr, type val, int memmodel);
type __atomic_fetch_sub (type *ptr, type val, int memmodel);
type __atomic_fetch_and (type *ptr, type val, int memmodel);
type __atomic_fetch_xor (type *ptr, type val, int memmodel);
type __atomic_fetch_or (type *ptr, type val, int memmodel);
type __atomic_fetch_nand (type *ptr, type val, int memmodel);

bool __atomic_test_and_set (void *ptr, int memmodel);
void __atomic_clear (bool *ptr, int memmodel);

void __atomic_thread_fence (int memmodel);
__atomic_signal_fence (int memmodel);

bool __atomic_always_lock_free (size_t size, void *ptr);
bool __atomic_is_lock_free (size_t size, void *ptr);

__atomic函数族和c++11 atomic的关系 (2.2)

第1节讲的是GCC对c++11标准中的atomic的实现,这些实现是基于GCC内的__atomic函数族的。__atomic函数族取代了之前旧的__sync函数族,主要区别是__atomic函数族允许用户指定memory-order/memory-model(作为参数),而__sync函数族不支持。__atomic函数族匹配了c++11内存模型的需求。实际上,__atomic函数族和c++11 atomic如此匹配以至于让人感觉它就是为了实现c++11而生的,但理论上讲,GCC是GNU Compliler Collection,__atomic函数族也可以用于c++11以外的其他编译器。

另外,我们可以直接使用__atomic函数族:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//可移植性差!!!
#include<iostream>

int main()
{
long long a, b;

__atomic_store_n(&a, 100, __ATOMIC_SEQ_CST);
b = __atomic_load_n(&a, __ATOMIC_SEQ_CST);

std::cout << b << std::endl;

return 0;
}

这段程序可以使用g++ (GCC) 4.8.5编译并运行(注意不用include额外的头文件),但它可移植性非常差:使用GCC以外的编译器无法编译,甚至其他版本的GCC也可能无法编译(假如若干版本以后,__atomic函数族被替换了呢)。我们应该使用c++11的atomic,假如__atomic函数族被替换了,那么GCC也会基于新的函数族重新实现c++11 atomic(如果那时还兼容c++11的话)。

系统相关和系统无关的实现 (2.3)

__atomic函数族的实现包含两部分:

  • 系统结构无关部分
  • 系统结构相关部分

1,2,4,8字节的基础类型和指针类型:所有__atomic函数族都支持这些类型,系统结构无关部分提供了这些支持(系统相关的库可以覆盖吗?)。也就是说,无需安装并链接系统结构相关的库,就可以使用__atomic函数族来操作这些类型。
16字节的基础类型:如果系统结构支持,则__atomic函数族就都支持。所以,这些支持是系统相关的库提供的。也就是说,必须安装并链接系统相关的库(对于x64是libatomic-4.8.5-36.el7_6.2.x86_64)才能使用。
其他任意类型T:只有__atomic_load, __atomic_store, __atomic_exchange, __atomic_compare_exchange4个函数支持,这4个函数是__atomic_load_n, __atomic_store_n, __atomic_exchange_n, __atomic_compare_exchange_ngeneric版。若Tsize是1,2,4,8字节,则系统结构无关部分就可以支持它们。否则,需要系统结构相关的库。

例如,下面这段代码不比链接libatomic-4.8.5-36.el7_6.2.x86_64就可以编译并运行(也就是说,编译时就能确定系统结构无关部分就能支持struct A类的对象):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>

struct A {int a; int b;};

int main()
{
std::cout << std::boolalpha << __atomic_is_lock_free(sizeof(A), NULL) << std::endl;

A a1;
A a2 = {.a=400, .b=800};

__atomic_store(&a1, &a2, __ATOMIC_RELAXED);

std::cout << a1.a << std::endl;
std::cout << a1.b << std::endl;

return 0;
}

// g++ --std=c++11 test1.cpp
// ./a.out
// true
// 400
// 800

而下面这段代码必须链接libatomic-4.8.5-36.el7_6.2.x86_64(编译时就知道系统结构无关部分不能支持struct A):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<iostream>

struct A {int a; int b; int c; int d;};

int main()
{
std::cout << std::boolalpha << __atomic_is_lock_free(sizeof(A), NULL) << std::endl;

A a1;
A a2 = {.a=400, .b=800, .c=1600, .d=3200};

__atomic_store(&a1, &a2, __ATOMIC_RELAXED);

std::cout << a1.a << std::endl;
std::cout << a1.b << std::endl;
std::cout << a1.c << std::endl;
std::cout << a1.d << std::endl;

return 0;
}

// g++ --std=c++11 test1.cpp
// /tmp/ccRK4PZ5.o: In function `main':
// test1.cpp:(.text+0x14): undefined reference to `__atomic_is_lock_free'
// test1.cpp:(.text+0x7b): undefined reference to `__atomic_store_16'
// collect2: error: ld returned 1 exit status

// g++ --std=c++11 test1.cpp -latomic
// ./a.out
// true
// 400
// 800
// 1600
// 3200

甚至,下面这两段代码,前者可以不链接系统相关库,而后者必须链接:

1
2
3
4
5
6
7
8
9
10
11
#include<iostream>

int main()
{
std::cout << std::boolalpha << __atomic_is_lock_free(8, NULL) << std::endl;
return 0;
}

// g++ --std=c++11 test1.cpp
// [root@devbuild01 atomic]# ./a.out
// true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>

int main()
{
std::cout << std::boolalpha << __atomic_is_lock_free(16, NULL) << std::endl;
return 0;
}

// g++ --std=c++11 test1.cpp
// /tmp/ccQiENp6.o: In function `main':
// test1.cpp:(.text+0x14): undefined reference to `__atomic_is_lock_free'
// collect2: error: ld returned 1 exit status

// g++ --std=c++11 test1.cpp -latomic
// ./a.out
// true

memory-order/memory-model (2.4)

不同的线程在不同cpu上执行,它们都有自己的cache。它们对共享变量的修改可能只在自己的cache里。memory-model合并了两个方面需求:

    1. optimization barrier;
    1. 线程间sync;

__ATOMIC_RELAXED (2.4.1)

最松散模式,没有optimization barrier,也没有线程间sync;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//thread-1
y.store(20, memory_order_relaxed)
x.store(10, memory_order_relaxed)

//thread-2
if (x.load(memory_order_relaxed) == 10)
{
assert(y.load(memory_order_relaxed) == 20) /* assert A */
y.store (10, memory_order_relaxed)
}

//thread-3
if (y.load (memory_order_relaxed) == 10)
assert (x.load(memory_order_relaxed) == 10) /* assert B */

thread-2的assert A和thread-3的assert B都可能失败。在__ATOMIC_RELAXED模式下,一个线程不能依赖另一个线程内的语句顺序(可能被重排)。
__ATOMIC_RELAXED只保证这样一个顺序:一旦thread-A对变量的修改被thread-B看见,那么thread-B就再也看不见这个变量以前的值(不能一会儿看见新值一会儿看见旧值)。

假设x的初始值是0:

1
2
3
4
5
6
7
8
thread-1
x.store (1, memory_order_relaxed)
x.store (2, memory_order_relaxed)

thread-2
y = x.load (memory_order_relaxed)
z = x.load (memory_order_relaxed)
assert (y <= z)

thread-2的assert永远不会失败,即thread-2一旦看见x=1就再看不见x=0;一旦看见x=2就再看不见x=1x=0

当程序员只想要一个变量是原子的,而不依赖这个变量来为其他共享数据提供线程间同步的时候,应该使用__ATOMIC_RELAXED模式。这种模式要求最松散,所以编译器能提供更大的优化。

__ATOMIC_RELAXED模式总结:

  • 一个线程不能依赖另一个线程内的不相关语句的执行顺序;
  • 可以依赖对同一个值得修改顺序;
  • 只保证一个变量是原子的,不能依靠它为其他共享数据提供线程间同步;

__ATOMIC_CONSUME (2.4.2)

__ATOMIC_ACQUIRE (2.4.3)

__ATOMIC_RELEASE (2.4.4)

__ATOMIC_ACQ_REL (2.4.5)

__ATOMIC_SEQ_CST (2.4.6)

__ATOMIC_SEQ_CST是sequentially consistent的缩写。它是最严格的模式,也是默认模式。

例1:

1
2
3
//thread-1               //thread-2
y = 1 if (x.load() == 2)
x.store(2); assert (y == 1)

__ATOMIC_SEQ_CST模式下,thread-2里的assert不可能失败。xy是不相关的,本来编译器可以重排y=1x.store(2)的顺序(这样thread-2的assert就会失败),但__ATOMIC_SEQ_CST要求线程间满足sequentially consistent:不相关的变量也必须满足happens-before关系,即,如果thread-2看到了x.store(2)操作,那么它必须要看到x.store(2)之前的所有操作(y=1)。编译优化器不能随意的跨atomic.store()来重排语句顺序。当然,也不能跨atomic.load()来重排语句顺序,看例2。

例2:

1
2
3
4
5
6
7
8
9
                      a = 0
y = 0
b = 1
------------------------------------------------------
//thread-1 //thread-2
x = a.load() while (y.load() != b)
y.store (b) ;
while (a.load() == x) a.store(1)
;

单看thread-1,while内有两条语句:1. a.load(); 2. 判断和x是否相等并跳转(je指令)。程序员的原意是这两个语句交替执行,直到ax不相等。不使用__ATOMIC_SEQ_CST的话,由于ax是不相关的,编译器可能把它优化成一个死循环(跨atomic.load()来重排语句顺序)。

例3:

假定xy的初始值都是0.

1
2
3
4
5
6
7
8
9
10
11
//thread-1
y.store(20);

//thread-2
x.store(10);

//thread-3
assert (y.load() == 20 && x.load() == 0)

//thread-4
assert (y.load() == 0 && x.load() == 10)

在例1里,y=1x.store(2)这两个不相关的操作在同一个线程里,而例3要表达的是,在__ATOMIC_SEQ_CST模式下,y.store(20)x.store(10)(这里y也是一个atomic变量)这两个不相关的操作,在不同的线程里也有严格的顺序。虽然这个顺序是在运行时决定的,但是一旦决定之后,所有线程都能看到相同的顺序:看见后者的时候,一定要求看见前者。

  • 假如运行时确定y.store(20)在前x.store(10)在后,那么thread-4的assert一定会失败;
  • 假如运行时确定x.store(10)在前y.store(20)在后,那么thread-3的assert一定会失败;

thread-3和thread-4的assert不可能都通过。也就是说,atomic变量在线程间也是满足happens-before关系。

__ATOMIC_SEQ_CST模式总结:

  • 满足happens-before关系:不相关的atomic操作也必须满足这个关系。一旦一个atomic操作被看见,那么它之前的操作结果都必须被看见。不同的线程内的atomic操作也是一样,只不过它们谁先谁后是运行时决定的。这个模式保证”consistency across all threads”,需要系统总线上的sync(可能比较expensive)。
  • atomic操作等同于optimization barrier:可以在atomic操作之间调整语句顺序,不能跨atomic操作调整语句顺序。其实这是happens-before原则衍生出来的:要所有atomic操作满足happens-before关系,自然不能跨atomic操作进行语句顺序调整。

lock free (2.5)

__atomic_always_lock_free系统结构无关的实现中(见第2.5节):GCC要能确定atomic变量不需要使用锁,就返回true;否则返回false。返回false不代表atomic变量必须使用锁,而是表示不知道:取决于系统结构。
__atomic_is_lock_free,假如能确定atomic变量不需要使用锁,就直接返回true(编译链接时就能确定,不需要链接系统结构相关的库)。否则,调用系统结构相关的库。

GCC不确定:

1
2
3
4
5
6
7
8
9
10
11
#include<iostream>

int main()
{
std::cout << std::boolalpha << __atomic_always_lock_free(16, NULL) << std::endl;
return 0;
}

// g++ --std=c++11 test1.cpp
// ./a.out
// false

在x64系统结构下,16字节的atomic变量不需要锁:

1
2
3
4
5
6
7
8
9
10
11
#include<iostream>

int main()
{
std::cout << std::boolalpha << __atomic_is_lock_free(16, NULL) << std::endl;
return 0;
}

// g++ --std=c++11 test1.cpp -latomic
// ./a.out
// true

c++11 atomic (3)

前面的内容都是在说GCC实现,这一节是c++11 atomic的标准,和实现无关。

memory order (3.1)

对应GCC的memory-order/memory-model(见第2.4节)。

1
2
3
4
5
6
memory_order_relaxed
memory_order_consume
memory_order_acquire
memory_order_release
memory_order_acq_rel
memory_order_seq_cst

atomic_is_lock_free (3.2)

std::atomic_is_lock_free函数用于查看一个std::atomic<T>* obj是否是lock free的。

头文件中,定义了这些宏

1
2
3
4
5
6
7
8
9
10
#define ATOMIC_BOOL_LOCK_FREE     /* unspecified */
#define ATOMIC_CHAR_LOCK_FREE /* unspecified */
#define ATOMIC_CHAR16_T_LOCK_FREE /* unspecified */
#define ATOMIC_CHAR32_T_LOCK_FREE /* unspecified */
#define ATOMIC_WCHAR_T_LOCK_FREE /* unspecified */
#define ATOMIC_SHORT_LOCK_FREE /* unspecified */
#define ATOMIC_INT_LOCK_FREE /* unspecified */
#define ATOMIC_LONG_LOCK_FREE /* unspecified */
#define ATOMIC_LLONG_LOCK_FREE /* unspecified */
#define ATOMIC_POINTER_LOCK_FREE /* unspecified */

它们的展开值:

  • 若为0:nerver lock free;即当前编译器选择使用锁来实现atomic。
  • 若为1:系统结构相关;
  • 若为2:always lock free;即当前编译器(例如GCC 4.8.5)能够提供lock free的实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>

int main()
{
std::cout << "ATOMIC_BOOL_LOCK_FREE : " << ATOMIC_BOOL_LOCK_FREE << std::endl;
std::cout << "ATOMIC_CHAR_LOCK_FREE : " << ATOMIC_CHAR_LOCK_FREE << std::endl;
std::cout << "ATOMIC_CHAR16_T_LOCK_FREE : " << ATOMIC_CHAR16_T_LOCK_FREE << std::endl;
std::cout << "ATOMIC_CHAR32_T_LOCK_FREE : " << ATOMIC_CHAR32_T_LOCK_FREE << std::endl;
std::cout << "ATOMIC_WCHAR_T_LOCK_FREE : " << ATOMIC_WCHAR_T_LOCK_FREE << std::endl;
std::cout << "ATOMIC_SHORT_LOCK_FREE : " << ATOMIC_SHORT_LOCK_FREE << std::endl;
std::cout << "ATOMIC_INT_LOCK_FREE : " << ATOMIC_INT_LOCK_FREE << std::endl;
std::cout << "ATOMIC_LONG_LOCK_FREE : " << ATOMIC_LONG_LOCK_FREE << std::endl;
std::cout << "ATOMIC_LLONG_LOCK_FREE : " << ATOMIC_LLONG_LOCK_FREE << std::endl;
std::cout << "ATOMIC_POINTER_LOCK_FREE : " << ATOMIC_POINTER_LOCK_FREE << std::endl;

return 0;
}


// g++ --std=c++11 test1.cpp
// ./a.out
// ATOMIC_BOOL_LOCK_FREE : 2
// ATOMIC_CHAR_LOCK_FREE : 2
// ATOMIC_CHAR16_T_LOCK_FREE : 2
// ATOMIC_CHAR32_T_LOCK_FREE : 2
// ATOMIC_WCHAR_T_LOCK_FREE : 2
// ATOMIC_SHORT_LOCK_FREE : 2
// ATOMIC_INT_LOCK_FREE : 2
// ATOMIC_LONG_LOCK_FREE : 2
// ATOMIC_LLONG_LOCK_FREE : 2
// ATOMIC_POINTER_LOCK_FREE : 2

这表明,GCC 4.8.5能够为这些类型提供无锁atomic实现(不管在何种系统结构上)。
另外需要提一下,atomic_flag一定是lock free的,这是c++11标准规定的(只要支持c++11的编译器,就得提供atomic_flag的lock free实现),所以atomic_flag都不支持is_lock_free函数(见第1.10节的表)

在x64系统结构下,这段程序运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <atomic>

struct A1 {char a;};
struct A2 {short a;};
struct A4 {char a[4];};
struct A8 {int a; short b; char c[2];};
struct A16 {int a[4];};
struct A32 {int a[8];};

struct A12 {int a, b, c;};
struct PaddedA12 {int a, b, c; char pad[4];};

int main()
{
std::atomic<A1> a1;
std::atomic<A2> a2;
std::atomic<A4> a4;
std::atomic<A8> a8;
std::atomic<A16> a16;
std::atomic<A32> a32;

std::atomic<A12> a12;
std::atomic<PaddedA12> pa12;

std::cout << std::boolalpha
<< "std::atomic<A1> lock free: " << std::atomic_is_lock_free(&a1) << std::endl
<< "std::atomic<A2> lock free: " << std::atomic_is_lock_free(&a2) << std::endl
<< "std::atomic<A4> lock free: " << std::atomic_is_lock_free(&a4) << std::endl
<< "std::atomic<A8> lock free: " << std::atomic_is_lock_free(&a8) << std::endl
<< "std::atomic<A16> lock free: " << std::atomic_is_lock_free(&a16) << std::endl
<< "std::atomic<A32> lock free: " << std::atomic_is_lock_free(&a32) << std::endl
<< "std::atomic<A12> lock free: " << std::atomic_is_lock_free(&a12) << std::endl
<< "std::atomic<PaddedA12> lock free: " << std::atomic_is_lock_free(&pa12) << std::endl
;
return 0;
}

// g++ --std=c++11 test1.cpp -latomic
// ./a.out
// std::atomic<A1> lock free: true
// std::atomic<A2> lock free: true
// std::atomic<A4> lock free: true
// std::atomic<A8> lock free: true
// std::atomic<A16> lock free: true
// std::atomic<A32> lock free: false
// std::atomic<A12> lock free: false
// std::atomic<PaddedA12> lock free: true

这表明在x64系统结构下,对于1,2,4,8,16字节的数据,GCC提供lock free的atomic实现。当然,通过前面的分析我们知道,对于1,2,4,8字节的数据,GCC无需依赖系统结构就能提供lock free的实现(把std::atomic<A16>, std::atomic<A32>, std::atomic<A12>, std::atomic<PaddedA12>注释掉,可以不链接libatomic库);16字节数据能够lock free,是因为x64系统结构支持。

写的不错,有赏!