栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++ 11多线程中互斥锁、原⼦操作和自旋锁

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++ 11多线程中互斥锁、原⼦操作和自旋锁


目录
  •  简介
  •  面试题(i=0,在两个线程中执行i++操作100次)
  •  加锁方式
  •  原子操作方式
  •  自旋锁方式

 简介

通过一个面试题,对C++多线程互斥锁、原子操作和自旋锁进行学习和总结。

如有理解不对的地方,欢迎各位指出,大家共同交流和学习。 如有帮助,请点赞加支持! 送人玫瑰手有余香!

 面试题(i=0,在两个线程中执行i++操作100次)

i=0,在两个线程中执行i++操作100次,最终的结果是多少?
答案:结果在2-200之间,具体不固定。
下面,以一个例子进行演示,在windows平台,vs2017编译器下,i=0,在两个线程中执行i++操作100000次(100次达不到预期效果,因此,这里以100000次为例),代码如下:

#include
#include
#include
#include
using namespace std;

//线程函数
void Mythread(int* num)
{
	for (int i = 0; i < 100000; i++)
	{
		(*num)++;
	}
}

void test01()
{
	int num = 0;
	int threadNum = 2;     //线程个数
	vector m_thread;
	m_thread = vector(threadNum);
	
	for (auto i = 0; i < threadNum; i++)
	{
		m_thread[i] = std::thread(Mythread, &num);

	}
	for (auto i = 0; i < threadNum; i++)
	{
		m_thread[i].join();

	}
	cout <<"结果:"<
	
	test01();
	return 0;
}

预期结果为:200000

实际测试结果如下:

第一次运行结果:

第二次运行结果:

第三次运行结果:

总结,每次执行结果不固定,但是均在2-200000之间。为什么会这样那?

因为,num变量属于临界资源,两个线程在进行累加时,会存在竞争,而num++操作不是原子操作,实际由三部分组成:

  1. 每次先把num从内存复制到寄存器
  2. 然后执行加1操作
  3. 然后再把num复制到内存中

详细的解释,网上很多,可以自行搜索。

如何保障达到我们预期的结果那?
通常情况下,要想对多个操作实现原子性的操作,可以通过加锁的方式进行保证。如C++11 的互斥锁(mutex)、互斥变量、自旋锁等。

 加锁方式

C++ 11 mutex

#include
#include
#include

#include
#include        //新增
using namespace std;

mutex mut;   		   //新增    定义互斥锁    
void Mythread(int* num)
{
	for (int i = 0; i < 100000; i++)
	{
		mut.lock();    //新增    加锁
		(*num)++;
		mut.unlock();  //新增    释放锁
	}
}


void test02()
{
	int num = 0;
	int threadNum = 2;     //线程个数
	vector m_thread;
	
	m_thread = vector(threadNum);
	
	for (auto i = 0; i < threadNum; i++)
	{
		m_thread[i] = std::thread(Mythread, &num);

	}
	for (auto i = 0; i < threadNum; i++)
	{
		m_thread[i].join();

	}
	cout <<"结果:"<
	test02();

	system("pause");
	return 0;
}

执行多次,结果,均能正确输出:200000

 原子操作方式

原子操作,就是多线程程序中不可并行执行的最小操作,即同一时刻,有且只能有一个线程对其执行。

C++ 11 提供了一些原子变量,使用这些原子变量就不需要使用互斥锁来保护该变量了,用起来很简洁。

#include
#include
#include
#include     //新增
#include
#include        
using namespace std;


void Mythread(atomic& num)    //修改
{
	for (int i = 0; i < 100000; i++)
	{		
		num++;	
	}
}


void test03()
{
	//int num = 0;
	std::atomic num = 0;
	int threadNum = 2;     //线程个数
	vector m_thread;
	
	m_thread = vector(threadNum);
	
	for (auto i = 0; i < threadNum; i++)
	{
		m_thread[i] = std::thread(Mythread, &num);

	}
	for (auto i = 0; i < threadNum; i++)
	{
		m_thread[i].join();

	}
	cout <<"结果:"<
	test03();

	system("pause");
	return 0;
}

结果,也能正确输出:200000

使用原子变量,就不需要再敌不过一互斥量了,更方便、简洁。

可以看出原子变量是线程安全的,而普通变量不是。

 自旋锁方式

C++11原子类型提供了memory_order_acquire和memory_order_release原子类型内存操作。

memory_order_acquire类似互斥锁的lock,memory_order_release类似unlock。

使用原子布尔类型配合它们可以实现自旋锁,用来解决资源互斥问题。

#include
#include
#include

#include
#include
#include
using namespace std;
//实现自旋锁
class SpinLock {	
public:
	void lock() 
	{
		while (flag.test_and_set(memory_order_acquire));
	}
	void unlock() 
	{
		flag.clear(memory_order_release);
	}
private:
	std::atomic_flag flag;
};

SpinLock spinLock;
void Mythread(int& num)
{
	for (int i = 0; i < 100000; i++)
	{
		spinLock.lock();
		num++;
		spinLock.unlock();
	}
}


void test04()
{
	int num = 0;

	int threadNum = 2;     //线程个数
	vector m_thread;
	
	m_thread = vector(threadNum);
	
	for (auto i = 0; i < threadNum; i++)
	{
		m_thread[i] = std::thread(Mythread, ref(num));

	}
	for (auto i = 0; i < threadNum; i++)
	{
		m_thread[i].join();

	}
	cout <<"结果:"<
	test04();
	
	system("pause");
	return 0;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/938739.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号