无锁编程

  C++ 11 使用 atomic 原子操作实现无锁编程。修改原子类型的操作是原子操作,原子操作不会被线程调度机制打断,可以保证数据一致性。

1. 原子类型的使用


1.1 基本类型的原子类型


例 1:

atomic<int> num(0);
void add() {
	for (int i = 0; i < 100000; i++) // 加10万次。
		num++;
}
int main() {
	thread t1(add);
	thread t2(add);
	t1.join();
	t2.join();
	cout << num << endl;
}

  第 1 行使用原子类型的 num,第 11 行输出为 20 万。如果第 1 行使用 int 类型的 num,第 13 行输出会小于 20 万。

  当临界区是基本类型时,使用原子类型特化后,当作基本类型使用,如例 1 的第 1 行。
  当临界区是复合类型时,使用原子类型特化后,却不能当作普通复合类型使用,比如无法直接使用成员引用运算符引用成员。如例 2,无法通过 sum1.data 引用成员 data。

1.2 复合类型的原子类型


例 2:

struct Example {
	Example(int x) :sum(x) {}
	void add() {
		for (int i = 0; i < 100000; i++) {
			Item old = sum.load(memory_order_seq_cst);
			while (!sum.compare_exchange_weak(old, old.data + 1));
		}
	}
	struct Item {
		Item(int x = 0) :data(x) {}
		int data;
	};
	atomic<Item> sum;
};

int main() {
	Example ex(0);
	thread th1 = thread(&Example::add, &ex);
	thread th2 = thread(&Example::add, &ex);
	th1.join();
	th2.join();
	cout << ex.sum.load().data << endl;
}

  函数 add 操作的是特化复合类型 Item 得到的 sum,关键部分是第 5、6 行,其中第 6 行是原子操作:

  • 第 5 行获取临界区 sum 的当前值 old,其实就是临界区的一个拷贝。
  • 第 6 行:如果 sum 等于 old,用 old.data + 1 更新 sum 并返回 true。否则用 sum 更新 old 并返回 false,重新开始原子操作。

函数compare_exchange_weak

desire = now; // 进入原子操作时期望临界区是这个值。
now.compare_exchange_weak(desire, update);
  • 当前值 now 与期望值 desire 相等时,修改当前值 now 为设定值 update,返回 true。
  • 当前值 now 与期望值 desire 不等时,将期望值 desire 修改为当前值 now,返回 false。
  • 这个函数可能在满足 true 的情况下仍然返回 false,所以只能在循环里使用,否则可以使用它的 strong 版本。

原子操作理解

  使用 while 循环:获取临界区对象的值并记录,开始原子操作。原子操作不会被线程调度机制打断。但进入原子操作需要时间,这段时间内临界区可能被其它线程修改,所以原子操作的第 1 步就是比较进入原子操作前后临界区对象的值是否被其它线程修改。如果被其它线程修改了,就更新记录值重新开始原子操作。如果没有被其它线程修改,就执行原子操作的第 2 步:修改它的值

  例 3 主要演示怎么处理具有复杂构造函数的临界区对象,这样的对象才具有一般性。

例 3:

struct Example {
	Example(int x, int y) {
		Item temp(x, y);
		sum.store(temp, memory_order_seq_cst);
	}
	void add() {
		for (int i = 0; i < 100000; i++) {
			Item old = sum.load(memory_order_seq_cst);
			Item nov = Item(old.data1 + 1, old.data2 + 1);
			while (!sum.compare_exchange_weak(old, nov))
				nov = Item(old.data1 + 1, old.data2 + 1);
			// while (!sum.compare_exchange_weak(old, Item(old.data1 + 1, old.data2 + 1)));
		}
	}
	struct Item {
		Item() = default;
		Item(int x, int y) :data1(x), data2(y) {}
		int data1, data2;
	};
	atomic<Item> sum;
};

  临界区是具有复杂构造函数的对象时:

  • 使用第 3、4 行的方式初始化临界区对象。
  • 主要操作有第8 至 11 行。注意不能缺少第 11 行,因为函数 compare_exchange_weak 会更新 old 的值,但不会更新 nov 的值
  • 还可以使用第8、12行,和第8 至 11 行等价。

2. 原子类型的探究


1. 原子操作的内存顺序

// 1. 松散
memory_order_relaxed
// 2. 获得 - 释放
memory_order_consume
memory_order_acquire
memory order_release
memory_order_acq_rel
// 3. 顺序一致
memory_order_seq_cst
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值