内存模型与原子操作
内存模型
无论是怎么样的类型,都会存储在一个或多个内存位置上。虽然相邻位域中是不同的对象,但仍视其为相同的内存位置。
struct s1{
int i: 8;
int j: 4;
int a: 3;//8+4+3=15位<sizeof(int)即8字节(相同位域不同对象)
double b;//8字节
};//8+8=16字节
struct s2{
int i: 8;
int j: 4;//8+4=12位<sizeof(int)即8字节(相同位域不同对象)
double b;//8字节
int a:3;//3位<sizeof(int)即8字节
};//8+8+8=24字节
四个需要牢记的原则:
- 每个变量都是对象,包括其成员变量的对象。
- 每个对象至少占有一个内存位置。
- 基本类型都有确定的内存位置(无论类型大小如何,即使他们是相邻的或是数组的一部分)。
- 相邻位域是相同内存中的一部分。
为了避免条件竞争,线程就要以一定的顺序执行。
- 第一种方式:互斥量确定了线程访问的顺序,避免未定义行为的发生。
- 另一种方式:原子操作未指定线程访问顺序,但拉回定义行为的区间。
在初始化开始阶段,线程对象确定好修改的顺序。大多数情况下,这个顺序不同于执行中的顺序,但在给定的程序中,所有线程都需要遵守这个顺序。
- 非原子类型需要使用同步操作,使线程遵守修改顺序。
- 而原子操作,编译器有责任去做同步。
所有线程都要遵守程序中每个独立对象的修改顺序,但没有必要遵守在独立对象上的操作顺序。
原子操作和原子类型
原子操作:不可再分割的操作(当然自然科学中有夸克,但是只是个名称不抬杠)。如果读取操作对象是原子操作,其它操作也是原子的。
标准原子类型
定义在头文件<atomic>中。语言中将文件中的类型定义为原子的,也能用互斥锁模拟原子操作。
大部分原子类型可通过特化std::atomic<>
得到,几乎都有成员函数is_lock_free()
:
- 如果原子操作是直接用原子CPU指令实现无锁,返回true;
- 是内部使用锁结构,返回false。(内部没有互斥量的实现,才能有性能提升)
注:除std::atomic_flag
不提供is_lock_free()
,为简单布尔标志必然无锁。可以此为基础实现简单锁,进而实现其他基础原子类型。
C++17中有static constexpr bool is_always_lock_free;
,返回true硬件上是无锁类型。
标准库提供了一组宏(对应于内置原子类型),编译时对各种整型原子操作是否无锁进行判别(有锁,其值为0;无锁,其值为2;无所状态运行时才能决定,其值为1):
-
ATOMIC_BOOL_LOCK_FREE
-
ATOMIC_CHAR_LOCK_FREE
-
ATOMIC_CHAR16_T_LOCK_FREE
-
ATOMIC_CHAR32_T_LOCK_FREE
-
ATOMIC_WCHAR_T_LOCK_FREE
-
ATOMIC_SHORT_LOCK_FREE
-
ATOMIC_INT_LOCK_FREE
-
ATOMIC_LONG_LOCK_FREE
-
ATOMIC_LLONG_LOCK_FREE
-
ATOMIC_POINTER_LOCK_FREE
内置原子类型备选名于其相关的std::stomic<>
特化(避免两种混用):
原子类型 | 相关特化类 |
---|---|
atomic_bool | std::atomic |
atomic_char | std::atomic |
atomic_schar | std::atomic |
atomic_uchar | std::atomic |
atomic_int | std::atomic |
atomic_short | std::atomic |
atomic_long | std::atomic |
atomic_llong | std::atomic |
atomic_char16_t | std::atomic<char16_t> |
atomic_char32_t | std::atomic<char32_t> |
atomic_wchar_t | std::atomic<wchar_t> |
标准原子类型定义和对应的内置类型定义:
原子类型定义 | 标准库中相关类型定义 |
---|---|
atomic_int_least8_t | int_least8_t |
atomic_int_least16_t | int_least16_t |
atomic_int_least32_t | int_least32_t |
atomic_int_least64_t | int_least64_t |
atomic_int_fast8_t | int_fast8_t |
atomic_int_fast16_t | int_fast16_t |
atomic_int_fast32_t | int_fast32_t |
atomic_int_fast64_t | int_fast64_t |
atomic_intptr_t | intptr_t |
atomic_size_t | size_t |
atomic_ptrdiff_t | ptrdiff_t |
atomic_intmax_t | intmax_t |
注:原类型名前加上atomic_
;signed
写为s
;unsigned
写为u
;long long
写为ll
。
原子类型没有传统意义上的拷贝构造函数和拷贝赋值操作符(返回atomic&),但可隐式转化成对应内置类型(支持赋值)。赋值操作返回atomic而非atomic&;命名函数返回操作值。
//构造函数
atomic() noexcept = default;
constexpr atomic( T desired ) noexcept;
atomic( const atomic& ) = delete;//删除
//赋值
T operator=( T desired ) noexcept;
T operator=( T desired ) volatile noexcept;
atomic& operator=( const atomic& ) = delete;//删除
atomic& operator=( const atomic& ) volatile = delete;//删除
//std::atomic通过coyping函数实例化必须使用T类型而非T&,如果不满足以下值为false
std::is_trivially_copyable<T>::value
std::is_copy_constructible<T>::value
std::is_move_constructible<T>::value
std::is_copy_assignable<T>::value
std::is_move_assignable<T>::value
每个原子类型及其所能使用的操作:
成员函数 | atomic_flag | atomic<bool> | atomic<T*> | atomic<内置类型> | atomic<其他类型> |
---|---|---|---|---|---|
test_and_set | Y | ||||
clear | Y | ||||
is_lock_free | Y | Y | Y | Y | |
load | Y | Y | Y | Y | |
store | Y | Y | Y | Y | |
exchange | Y | Y | Y | Y | |
compare_exchange_weak, compare_exchange_strong | Y | Y | Y | Y | |
fetch_add, += | Y | Y | |||
fetch_sub, -= | Y | Y | |||
fetch_or, |= | Y | ||||
fetch_and, &= | Y | ||||
fetch_xor, ^= | Y | ||||
++, – | Y | Y |
没有除法、乘法及移位操作,但可以使用compare_exchange_weak()
完成。
函数原子化操作