文章目录
原子操作
什么是原子操作
原子操作简单来说就是对于一个数据不可再细分的操作,例如读,写等基本操作就是原子操作。
为什么要有原子操作
当我们写出以下代码并运行的时候,我们会惊讶于结果的不可思议,结果会在10000-20000之间来回横跳,为什么?因为线程并发的时候,两个线程对于同一变量的修改可能会在时间点上重合并覆盖,导致一些步骤丢失。为了解决这个问题可以使用我们以前学到的互斥锁进行限制,然而,为了一个基本数据的每次修改都产生一个锁,对资源的开销是很大的,我们迫切需要一个更优雅的解决办法,让一个数据类型自带这种功能:在进行原子操作时自发产生互斥性。我们不可能为每一个数据类型都创造对应新的数据类型,但可以进行模板编程。于是, atomic 模板类应运而生。
cpp
int value = 0;
void testFunc() {
for (size_t i = 0; i < 10000; i++){
value++;
}
}
int main(){
thread t1(testFunc);
thread t2(testFunc);
t1.join(); t2.join();
cout<<value<<endl;
}
基本实现
atomic模板类该如果和使用?这里给出代码看注释讲解。
需要注意的是,在模板类中拷贝和赋值操作被删除。
cpp
#include<iostream>
#include<thread>
#include<atomic> //原子操作头文件
//atomic 模板类:atomic<int> foo = 1;
using namespace std;
int value = 0;
atomic<int> a_value = 0;
void testFunc() {
for (size_t i = 0; i < 10000; i++){
value++;
a_value++;
}
}
void test1() {
thread t1(testFunc);
thread t2(testFunc);
t1.join();
t2.join();
cout << value << endl;
cout << a_value << endl;
}
int main(){
test1();
return 0;
}
我们发现,通过原子操作,数据精确无误地进行了修改。
store、load、exchange 操作
因为拷贝、赋值操作在atomic模板中被删除,所以模板也提供了更安全的函数实现方式来实现数据的存储(写)、加载(读)、交换(改)的操作,分别是store、load、exchange成员函数。对于内存操作顺序,也给出了枚举类型,列举如下,而实际操作中如果不提严苛的要求,往往省略。
cpp
/*
memory_order:内存顺序
强顺序:执行顺序和代码顺序一致
弱顺序:执行顺序会被处理器适当调整
relaxed:对于执行顺序不做要求
无效:consume:本线程中,后续原子操作必须在本原子类型操作后再开始
无效:acquire:本线程中,后续原子读操作必须在本原子类型操作后再开始
release:本线程中,后续原子写操作必须在本原子类型操作后再开始
无效:acq_rel:acquire和release的结合
seq_cst:全部读写都按照顺序
*/
关于三个成员函数的操作给出示例代码:
cpp
#include<iostream>
#include<atomic>
atomic_int iNum = 2; //相当于atomic<int> iNum = 2;
//可以进入atomic头文件中找到更多形如atomic_xxx的类型,便于书写
void test2(){
//写操作
iNum.store(4);
cout << iNum << endl;
//读操作
int a = iNum.load();
cout << a << endl;
//修改操作
iNum.exchange(2);
cout << iNum << endl;
}
int main() {
test2();
return 0;
}
自旋锁的简单实现
在atomic模板类中,有一个类型与众不同:atomic_flag,常常用它来做自旋锁。
自旋锁有什么性质
atomic_flag:无锁的原子类型
<1>.test_and_set(): 没有设置标记,设置一下,如果设置了,返回true
<2>.clear(): 清除标记,让下一次<1>返回false
其他原子类型可以使用<3>.is_lock_free()来判断是否无锁
互斥锁:资源竞争的时候申请者进入挂起状态,需要被唤起:notify系列
自旋锁:不会挂起,而是一直循环等待锁的释放,无需唤起:无需notify
自旋锁实例代码
cpp
#include<iostream>
#include<thread>
#include<atomic>
using namespace std;
atomic_flag Lock = ATOMIC_FLAG_INIT; //其实就是{}
void test3(int n) {
while (Lock.test_and_set()){
printf("等待中......%d\n", n);
}
printf("线程完成:%d\n", n);
}
void test4(int n) {
printf("线程开始启动:%d\n", n);
this_thread::sleep_for(1ms);
Lock.clear();
printf("线程运行结束\n");
}
int main() {
Lock.test_and_set();
thread t1(test3, 1);
thread t2(test4, 2);
t1.join();
t2.join();
return 0;
}
上述代码中,线程1一直处于while循环中在此期间可以做循环内的事情,在线程2执行完后立即跳出循环,继续往下执行,并没有需要来自线程2的通知。