C++ atomic与memory_order 详解

**

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();
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值