c++11互斥锁:mutex

c++11互斥锁:mutex,第1张

c++11互斥锁:mutex

  • 互斥锁:mutex
  • 成员函数
    • 构造函数
    • lock()
    • unlock()
    • try_lock()
  • 应用测试
    • lock/unlock
    • try_lock/unclok
    • lock_guard
  • 死锁
  • 死锁测试

互斥锁:mutex

std::mutex 是C++中最基本的互斥量,std::mutex 对象提供了独占所有权的特性,即不支持递归地对std::mutex 对象上锁。
std::recursive_lock 则可以递归地对互斥量对象上锁。

成员函数 构造函数

std::mutex 不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。

lock()

调用线程将该互斥量锁住,线程调用该函数会发生以下3 种情况:
(a)如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用unlock之前,该线程一直拥有该锁。
(b)如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
(c)如果当前互斥量被当前调用线程锁住,则会产生死锁

unlock()

解锁,释放对互斥量的所有权。

try_lock()

尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。
线程调用该函数会出现下面3 种情况:
如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用unlock 释放互斥量。
(a)如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用unlock 释放互斥量。
(b)如果当前互斥量被其他线程锁住,则当前调用线程返回false,而并不会被阻塞掉。
(c)如果当前互斥量被当前调用线程锁住,则会产生死锁。

应用测试 lock/unlock

代码演示:

#include 
#include 
#include 

using namespace std;

volatile int counter(0); // non-atomic counter
//volatile 表示不进行优化  多次循环不把i保存在寄存器,每次从内存获取。


mutex mtx; // locks access to counter
void increase10Ktime()
{
	for (int i = 0; i < 10000; i++)
	{
		mtx.lock();
		counter++;
		mtx.unlock();
	}
}
int main()
{
	thread ths[10];
	for (int i = 0; i < 10; i++)
	{
		ths[i] = thread(increase10Ktime);
	}
	for (auto& th : ths)
		th.join();
	cout << "after successful increase :" << counter << endl;
	return 0;
}

运行结果:

如果使用互斥锁进行控制。
代码演示:

#include 
#include 
#include 

using namespace std;

volatile int counter(0); // non-atomic counter
//volatile 表示不进行优化  多次循环不把i保存在寄存器,每次从内存获取。


mutex mtx; // locks access to counter
void increase10Ktime()
{
	for (int i = 0; i < 10000; i++)
	{
		counter++;
	}
}
int main()
{
	thread ths[10];
	for (int i = 0; i < 10; i++)
	{
		ths[i] = thread(increase10Ktime);
	}
	for (auto& th : ths)
		th.join();
	cout << "after successful increase :" << counter << endl;
	return 0;
}

运行结果:

try_lock/unclok

代码演示:

#include 
#include 
#include 
using namespace std;

volatile int counter(0); // non-atomic counter

mutex mtx; // locks access to counter

void increase10Ktime()
{
	for (int i = 0; i < 10000; i++)
	{
		while (!mtx.try_lock());	//没有锁死等
		counter++;
		mtx.unlock();
	}
}

int main()
{
	thread ths[10];
	for (int i = 0; i < 10; i++)
	{
		ths[i] = thread(increase10Ktime);
	}
	for (auto& th : ths)
		th.join();
	cout << "after successful increase :" << counter << endl;
	return 0;
}

运行结果:

lock_guard

在lock_guard 对象构造时,传入的Mutex 对象(即它所管理的Mutex 对象)会被当前线程锁住。
在lock_guard 对象被析构时,它所管理的Mutex 对象会自动解锁。

由于不需要程序员手动调用lock 和unlock 对Mutex 进行上锁和解锁 *** 作,因此这也是最简单安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的Mutex 对象可以正确进行解锁 *** 作,极大地简化了程序员编写与Mutex 相关的异常处理代码。

与Mutex RAII 相关,方便线程对互斥量上锁。

不使用lock_guard
代码演示:

#include 
#include 
#include 
using namespace std;
mutex mtx;
void printEven(int i)
{
	if (i % 2 == 0)
		cout << i << " is even" << endl;
	else
		throw logic_error("not even");	//抛出异常
}
void printThreadId(int id)
{
	try {
		mtx.lock();
		printEven(id);
		mtx.unlock();
	}
	catch (logic_error&) {
		cout << "exception caught" << endl;
	}
}
int main()
{
	thread ths[10]; //spawn 10 threads
	for (int i = 0; i < 10; i++)
	{
		ths[i] = thread(printThreadId, i + 1);
	}
	for (auto& th : ths)
		th.join();
	return 0;
}

运行结果:

抛出异常之后,就会出现抛出异常的线程只有加锁,没有释放锁的过程。
只要有一个线程没有释放,和这个锁相关的所有线程都会阻塞。

代码优化:

#include 
#include 
#include 

using namespace std;

mutex mtx;

void printEven(int i)
{
	if (i % 2 == 0)
		cout << i << " is even" << endl;
	else
		throw logic_error("not even");	//抛出异常
}

void printThreadId(int id)
{
	try {
		//RAII:	mtx资源获取即初始化。
		lock_guard lck(mtx); //栈自旋抛出异常时栈对象自我析构。
			printEven(id);
	}
	catch (logic_error&) {
		cout << "exception caught" << endl;
	}
	//离开当前作用于自动释放mtx锁。
}

int main()
{
	thread ths[10]; //spawn 10 threads
	for (int i = 0; i < 10; i++)
	{
		ths[i] = thread(printThreadId, i + 1);
	}
	for (auto& th : ths)
		th.join();
	return 0;
}

运行结果:

可以看到:当前线程抛出异常不影响其他线程。

死锁

A拿到自己的锁没有释放,但是要去占用B的锁。B拿到自己的锁没有释放,但是要去占用A的锁。

A已经获取一把锁,又试图获取已获得的锁。

死锁测试

死锁的原因:
container 试图多次去获取己获得的锁。std::recursive_mutex 允许多次获取相同的mutex。

C++中STL 中的容器,是非线程安全的。

代码演示:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

template 
class container
{
public:
	void add(T element)
	{
		_mtx.lock();	//已经上锁 再次上锁	导致死锁
		_elements.push_back(element);
		_mtx.unlock();
	}

	void addrange(int num, ...)
	{
		va_list arguments;
		va_start(arguments, num);

		for (int i = 0; i < num; i++)
		{
			_mtx.lock();//上锁
			add(va_arg(arguments, T));
			_mtx.unlock();
		}
		va_end(arguments);
	}
	void dump()
	{
		_mtx.lock();
		for (auto e : _elements)
			cout << e << endl;
		_mtx.unlock();
	}
private:
	mutex _mtx;
	vector _elements;
};

void func(container& cont)
{
	cont.addrange(3, rand(), rand(), rand());
}

int main()
{
	srand((unsigned int)time(0));

	container cont;

	thread t1(func, ref(cont));
	thread t2(func, ref(cont));
	thread t3(func, ref(cont));

	t1.join();
	t2.join();
	t3.join();
	cont.dump();
	return 0;
}

运行结果:

使用递归锁可以解决死锁:
代码演示:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

template 
class container
{
public:
	void add(T element)
	{
		unique_lock lck(_mtx);//使用递归锁
		_elements.push_back(element);
		_mtx.unlock();
	}

	void addrange(int num, ...)
	{
		va_list arguments;
		va_start(arguments, num);

		for (int i = 0; i < num; i++)
		{
			unique_lock lck(_mtx);//使用递归锁
			_mtx.lock();//上锁
			add(va_arg(arguments, T));
		}
		va_end(arguments);
	}
	void dump()
	{
		unique_lock lck(_mtx);//使用递归锁
		for (auto e : _elements)
			cout << e << endl;
	}
private:
	recursive_mutex _mtx;
	vector _elements;
};

void func(container& cont)
{
	cont.addrange(3, rand(), rand(), rand());
}

int main()
{
	srand((unsigned int)time(0));

	container cont;

	thread t1(func, ref(cont));
	thread t2(func, ref(cont));
	thread t3(func, ref(cont));

	t1.join();
	t2.join();
	t3.join();
	cont.dump();
	return 0;
}

运行结果:

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4751796.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-08
下一篇 2022-11-08

发表评论

登录后才能评论

评论列表(0条)

保存