C++11多线程(五)原子操作简单使用
以下都是原子的简单使用,后续深入了解原子操作后会再记录。
概念
什么是原子?
在计算机中我们可以将其理解为“原子是无法分割的最小个体”。
什么是原子操作?
通过操作原子来实现线程对资源的安全访问,听起来和锁是差不多的,但原子操作更接近于底层,所有效率会更好。互斥锁是一种对象,使你可以执行一系列互斥操作。而原子操作是互斥的单个操作,这意味着没有其他线程可以打断它。
原子操作在运行过程中是不会被打断的,这点需要我们记住,原子操作只有完成完成和未完成状态。正是这一点,我们也可以使用原子操作来保证线程的安全性。
C++11中虽然引入了lock_guard和unique_lock等等这些锁,但在实际过程中我们追求更高效的无锁操作时,我们就可以使用原子操作来实现无锁操作。
std::atomic
在使用之前我们需要注意引入头文件#include<atomic>
#include<atomic> //引入头文件
std::atomic<int> m_atomic ; //创建原子类
从上面我们可以很清楚的看出,这是一个类模板。
atomic通过将基础数据类型封装然后供给我们使用。就比如上文的atomic就是一个封装了int类型数据的原子。使用的时候你就把他理解为一个int类型的变量来进行相应的运算符操作,比如++,–,+=,-=等运算符(具体会在之后成员方法进行介绍)。
那原子操作到底有更快呢,下面通过实际代码来看一下。
#include<iostream>
#include<thread>
#include<mutex>
#include<atomic> //引入头文件
using namespace std;
atomic<int> x(0); //声明一个int类型的原子变量x,初始值为0
mutex mtx;
void f() { //这段代码是用来测试线程同步的,线程同步的概念可以理解为保证数据安全性
//也可以尝试一下不用原子而用in来运行,有关知识之后的线程概念部分会进行讲解
for (int i = 0; i < 100000000; i++) {//每一个线程对进行100000000次循环++
x++; //对原子进行++操作
};
}
int main() {
clock_t start = clock(); //获取运行启动时间
thread t1(f);
thread t2(f);
t1.join();
t2.join();
clock_t end = clock();//获取运行结束时间
cout << x << endl;
cout << "Run_Time:" << end - start << endl;
}
本次测试结果
再来看看使用锁
#include<iostream>
#include<thread>
#include<mutex>
#include<atomic>
using namespace std;
mutex mtx;
int x;
void f() {
//mtx.lock();
for (int i = 0; i < 100000000; i++) {
//注意是在循环里进行锁住而不是在函数入口,这样才能测试锁和原子的效率对比
//也可以用类似方法测试一下unique_lock和lock_guard,希望你还没有忘记
mtx.lock();
x++;
mtx.unlock();
}
//mtx.unlock();
}
int main() {
clock_t start = clock(); //获取运行启动时间
thread t1(f);
thread t2(f);
t1.join();
t2.join();
clock_t end = clock();//获取运行结束时间
cout << x << endl;
cout << "Run_Time:" << end - start << endl;
}
可以明显的看出效率差距。
atomic的方法
bool is_lock_free();
提供一个接口函数给使用者查询调用原子的实现方式
return true : 直接利用原子指令
return false : 利用编译器和库函数实现
atomic重载了这几个运算符
&=,++,+=,–,-=,=,^=,|=
这几个运算符的用法和基础数据类型是一样的,在这里不多做解释。
值得一提的是,atomic也禁用了拷贝构造函数。
atomic常用的方法也就load()和store()
atomic::load() 读原子操作
atomic::store() 写原子操作
这里稍微提一下 运算符赋值和 load()/store()的区别
load是取值,“=”是赋值,不是一回事,取值的方式有两种,一种是通过load取值,另一种直接用创建的示例取值(这种方式实际调用的是仿函数,即重载了“()”运算符,再通过仿函数调用 load() 实现取值) 2、赋值方式 store 和“=”运算符号,差别是,store采用memcpy进行值拷贝,而重载的“=”是通过逐字节赋值而且还是值交换的方式进行,效率比store慢;另外store可以直接使用 atomic对象进行赋值,并不是有接受atomic对象的store函数,而是传入的对象会先调用仿函数获取实际值作为store的返回值,而“=”运算符自带的删除,新定义的重载“=”的函数只能接受atomic管理的示例类型的值,例如 atomic_bool 利用“=”符号只能用类型 的值而不能用atomic_bool对象赋值
此外,原子操作还有运算函数fetch_add(),fetch_sub(),。
因为本人平时使用原子的计算也只是用运算符,资料也没找到太多,不深入解析。想更深入的了解原子操作可阅读源码或者自行查阅相关资料。
快速创建原子变量
C++11还提供了我们许多宏用来快速创建原子变量。
typedef atomic<bool> atomic_bool;
typedef atomic<char> atomic_char;
typedef atomic<signed char> atomic_schar;
typedef atomic<unsigned char> atomic_uchar;
typedef atomic<short> atomic_short;
typedef atomic<unsigned short> atomic_ushort;
typedef atomic<int> atomic_int;
typedef atomic<unsigned int> atomic_uint;
typedef atomic<long> atomic_long;
typedef atomic<unsigned long> atomic_ulong;
typedef atomic<long long> atomic_llong;
typedef atomic<unsigned long long> atomic_ullong;
typedef atomic<char16_t> atomic_char16_t;
typedef atomic<char32_t> atomic_char32_t;
typedef atomic<wchar_t> atomic_wchar_t;
typedef atomic<int8_t> atomic_int8_t;
typedef atomic<uint8_t> atomic_uint8_t;
typedef atomic<int16_t> atomic_int16_t;
typedef atomic<uint16_t> atomic_uint16_t;
typedef atomic<int32_t> atomic_int32_t;
typedef atomic<uint32_t> atomic_uint32_t;
typedef atomic<int64_t> atomic_int64_t;
typedef atomic<uint64_t> atomic_uint64_t;
typedef atomic<int_least8_t> atomic_int_least8_t;
typedef atomic<uint_least8_t> atomic_uint_least8_t;
typedef atomic<int_least16_t> atomic_int_least16_t;
typedef atomic<uint_least16_t> atomic_uint_least16_t;
typedef atomic<int_least32_t> atomic_int_least32_t;
typedef atomic<uint_least32_t> atomic_uint_least32_t;
typedef atomic<int_least64_t> atomic_int_least64_t;
typedef atomic<uint_least64_t> atomic_uint_least64_t;
typedef atomic<int_fast8_t> atomic_int_fast8_t;
typedef atomic<uint_fast8_t> atomic_uint_fast8_t;
typedef atomic<int_fast16_t> atomic_int_fast16_t;
typedef atomic<uint_fast16_t> atomic_uint_fast16_t;
typedef atomic<int_fast32_t> atomic_int_fast32_t;
typedef atomic<uint_fast32_t> atomic_uint_fast32_t;
typedef atomic<int_fast64_t> atomic_int_fast64_t;
typedef atomic<uint_fast64_t> atomic_uint_fast64_t;
typedef atomic<intptr_t> atomic_intptr_t;
typedef atomic<uintptr_t> atomic_uintptr_t;
typedef atomic<size_t> atomic_size_t;
typedef atomic<ptrdiff_t> atomic_ptrdiff_t;
typedef atomic<intmax_t> atomic_intmax_t;
typedef atomic<uintmax_t> atomic_uintmax_t;
上述都在源码中被定义过。
以上都是简单使用,后续深入了解原子操作后会再记录。