atomic
使用atomic可以保证数据读写的原子性,虽然mutex也能做到,但atomic的性能更好。atomic支持的类型有布尔类型,数值类型,指针类型,trivially_copyable类。
定义atomic时应该给一个初始值来初始化,或者调用atomic_init()来初始化。
atomic<bool> readyFlag(false);
atomic<bool> readyFlag;
atomic_init(&readyFlag, false);
使用store()写入值,load()读取值,这两个操作都是原子性的。
int main()
{
atomic<bool> readyFlag(false);
readyFlag.store(true);
if (readyFlag.load()) {
cout << "ok" << endl;
}
system("pause");
}
atomic类型也可以继续使用运算符,不过这些运算不是原子性的。
int main()
{
atomic<bool> readyFlag(false);
atomic<int> intValue(1);
readyFlag = true;
intValue = 100;
intValue++;
cout << intValue << endl;
system("pause");
}
atomic的原子性可以用免锁的CPU原子指令来实现,也可以通过加锁的方式实现,使用is_lock_free()可以判断atomic对象是否免锁。
int main()
{
atomic<bool> readyFlag(false);
atomic<int> intValue(1);
cout << intValue.is_lock_free() << endl;//1
cout << readyFlag.is_lock_free() << endl;//1
system("pause");
}
memory_order
处理器乱序执行和编译器指令重排可能造成数据读写顺序错乱,CPU缓存可能造成数据更新不及时,memory_order的作用是明确数据的读写顺序以及数据的写入对其它线程的可见性。
下面代码中,(4)所运行的断言有可能失败,原因是我们不能保证(1)执行的修改一定会被B线程看到,(1)执行的修改也许会由于乱序在(2)的后面执行,就算在(2)的前面执行,CPU的读写缓存也有可能没有写回内存(每个CPU内核可能有各自独立的缓存)。
由于x86架构是acquire-release语义,所以下面的代码在x86 CPU上运行永远不会断言失败。
int data;
std::atomic_bool flag{ false };
// Execute in thread A
void producer() {
data = 42; // (1)
flag.store(true); // (2)
}
// Execute in thread B
void consume() {
while (!flag.load()); // (3)
assert(data == 42); // (4)
}
C++标准库一共定义了6种memory_order,其中memory_order_acq_rel可以看作是memory_order_acquire和memory_order_release的合体:
typedef enum memory_order {
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
} memory_order;
内存顺序模型有下面四种,除非对性能要求很高,一般建议使用默认的序列一致顺序就行了,即使是序列一致顺序,性能也比mutex好。
- 宽松顺序(Relaxed ordering):原子操作带上memory_order_relaxed参数,仅保证操作是原子性的,不提供任何顺序约束。
- 释放获得顺序(Release-Acquire ordering):对于同一个atomic,在线程A中使用memory_order_release调用store(),在线程B中使用memory_order_acquire调用load()。这种模型保证在store()之前发生的所有读写操作(A线程)不会在store()后调用,在load()之后发生的所有读写操作(B线程)不会在load()的前调用,A线程的所有写入操作对B线程可见。
- 释放消费顺序(Release-Consume ordering):释放获得顺序的弱化版,对于同一个atomic,在线程A中使用memory_order_release调用store(),在线程B中使用memory_order_consume调用load()。这种模型保证在store()之前发生的所有读写操作(A线程)不会在store()后调用,在load()之后发生的依赖于该atomic的读写操作(B线程)不会在load()的前面调用,A线程对该atomic的带依赖写入操作对B线程可见。
- 序列一致顺序(Sequential consistency):原子操作带上memory_order_seq_cst参数,这也是C++标准库的默认顺序,也是执行代价最大的,它是memory_order_acq_rel的加强版,如果是读取就是 acquire语义,如果是写入就是 release 语义,且全部读写操作顺序均一致。
下面代码中value = 100不允许被移动到readFlag.store(true, memory_order_release)后面执行,assert(value == 100)不允许移动到while (!readFlag.load(memory_order_acquire))前面执行
atomic<bool> readFlag(false);
int value = 0;
void provider()
{
value = 100;
readFlag.store(true, memory_order_release);
}
void consumer()
{
while (!readFlag.load(memory_order_acquire));
assert(value == 100);
}
int main()
{
thread(provider).detach();
thread(consumer).detach();
system("pause");
}
atomic_flag
atomic_flag是一种简单的原子布尔类型,不能被拷贝,也不能 move 赋值,只支持两种操作,clear()设置值为false,test_and_set()设置值为true并返回之前的值。
一般使用ATOMIC_FLAG_INIT初始化atomic_flag使其处于 clear 状态 ,否则状态是未指定的。
使用atomic_flag实现的自旋锁:
atomic_flag g_lock = ATOMIC_FLAG_INIT;
void testFunc(int n)
{
for (int cnt = 0; cnt < 100; ++cnt) {
while (g_lock.test_and_set(memory_order_acquire)); // acquire lock
std::cout << "output from thread" << n << endl;
g_lock.clear(memory_order_release); // release lock
}
}
int main()
{
for (int i = 0; i < 5; ++i) {
thread(testFunc, i).detach();
}
cin.get();
}