C++多线程:原子操作和自旋锁

文章目录

原子操作

什么是原子操作

原子操作简单来说就是对于一个数据不可再细分的操作,例如读,写等基本操作就是原子操作。

为什么要有原子操作

当我们写出以下代码并运行的时候,我们会惊讶于结果的不可思议,结果会在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的通知。

相关推荐
白榆maple10 分钟前
(蓝桥杯C/C++)——基础算法(下)
算法
JSU_曾是此间年少15 分钟前
数据结构——线性表与链表
数据结构·c++·算法
此生只爱蛋1 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
何曾参静谧1 小时前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++
咕咕吖2 小时前
对称二叉树(力扣101)
算法·leetcode·职场和发展
九圣残炎2 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
lulu_gh_yu2 小时前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法
丫头,冲鸭!!!3 小时前
B树(B-Tree)和B+树(B+ Tree)
笔记·算法
Re.不晚3 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
ULTRA??3 小时前
C加加中的结构化绑定(解包,折叠展开)
开发语言·c++