C++11:原子操作

在多线程开发中,为了确保数据安全性,经常需要对数据进行加锁、解锁处理。C++11中引入了原子的概念,简而言之就是访问它时它自动加锁解锁,从而使软件开发更为简便。
原子可谓一个既简单又复杂的概念。简单到访问它时就跟单线程访问一块内存一样简单,复杂的地方在于它的实现涉及到各种内存模型,在优化中经常会遇到。
下面给出一个简单的原子示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
atomic_int val = { 0 };//这个类型也可以写作 atomic<int> 用于表示整型数据的原子
 
void icrement () {
    for (int i = 0; i < 100000000; i++) {
        val++;
    }
}
 
int main (int argc, char* argv []) {
    //创建两个线程
    thread t1 (icrement);
    thread t2 (icrement);
    //等待两个线程执行完
    t1.join ();
    t2.join ();
    cout << val << endl;
    return 0;
}

经过十几秒左右的等待后,代码执行完毕,结果不出所料,200000000。简单的原子操作差不多就是这样,atomic模板可以包括任何类型,另外原子的操作也与它本身的操作方式基本相同,因为原子模板重载了所有的运算符。

简单的说完了,说说复杂的原子概念。
假如一个原子,它长这样

1
atomic_int val = { 0 };

嗯,跟上面的相同。它实际上可以提供三种类型的操作:读、写、RMW(同时包括读写),通过三种类型的函数实现。
首先是读,比如 int i=val; 这样的代码,实际上是通过load函数实现。

1
int i = val.load (memory_order_seq_cst);

后面的参数代表内存顺序。这个的含义是顺序执行当前的原子操作。什么含义?含义就是,如果一个函数中对这个原子进行了多项操作,那么首先执行之前的原子操作,然后执行本条操作,最后执行之后的原子操作。说白了就是单线程的执行顺序。原子的操作过程并不是必须固定的,一个函数中如果有两条原子操作,那么首先执行后面操作,然后执行前面操作是完全可能的。这个在优化中经常会遇到。
然后是写操作,比如 val = i; 这样的操作

1
val.store (i, memory_order_seq_cst);

嗯,这儿也顺序执行,以免颠覆各位三观。
然后就是同时读写这样的操作了。 比如原子+=一个数之后同时可访问,通过compare_exchange这类函数实现。
然后,接下来说说内存访问模型了。一共有六种
1、memory_order_seq_cst 顺序执行,可用于读、写、RMW操作
2、memory_order_relaxed 乱序执行,可用于读、写、RMW操作
3、memory_order_acq_rel 首先执行之前的写操作,然后执行本条操作,然后执行之后的读操作,可用于RMW操作
4、memory_order_release 首先执行之前的写操作,然后执行本条操作,可用于写、RMW操作
5、memory_order_acquire 首先执行本条操作,然后执行后面的读操作,可用于读、RMW操作
6、memory_order_consume 首先执行本条操作,然后执行后面的读写操作,可用于读、RMW操作
基本的概念就是上面这些了,接下来动手实践吧

Published by

fawdlstty

又一只萌萌哒程序猿~~

发表评论

电子邮件地址不会被公开。 必填项已用*标注