c++多线程(3)之互斥量(mutex)、锁(lock,unlock,lock_guard)的应用

多线程中,多个线程对共享的数据进行访问,应该是最常见的应用。

如果多个线程都只是对共享数据进行读操作,还不会有问题,但是如果有的线程读数据,有的线程写数据,这时候就会出现问题。比如A线程写数据,但是写的这个过程进行到一半,B线程就去读,这个时候程序就会崩溃。这个时候就需要互斥量的出场啦!

mutex:

mutex互斥量是一个类,这个类有有一个lock()方法,和一个unlock()方法。如果第一次运行了lock()这个方法,而没有运行unlock()这个方法,第二次再运行lock()这个方法时,程序就会卡停在这里,只有当运行了unlock()这个方法运行后,第二个lock()方法才会运行通过。就是运用这种“锁”的机制就可以保证两段代码独立运行。

例子:


#include 
#include 
#include 
#include 

class A
{
public:
	void WriteFunction()
	{
		for (int i = 0; i < 1000; ++i)
		{
			
			std::cout << "向队列中添加一个元素" << std::endl;
			my_deque.push_back(i);
			
		}
	}
	void ReadFunction()
	{
		for (int i = 0; i < 1000; ++i)
		{
			
			if(!my_deque.empty())
			{
				std::cout << "读出队列的第一个元素: " << my_deque.front() << std::endl;
				my_deque.pop_front();
			}
			
		}
	}
private:
	std::deque my_deque;
	

};
int main()
{
	A a;
	std::thread my_thread_1(&A::WriteFunction, std::ref(a));

	std::thread my_thread_2(&A::ReadFunction, std::ref(a));

	my_thread_1.join();
	my_thread_2.join();
    std::cout << "Hello World!\n";
}

上面这段代码运行时,会出现错误的。原因就是上面提到的:读写可能会同时操作,出现错误。

修改的代码:


#include 
#include 
#include 
#include 

class A
{
public:
	void WriteFunction()
	{
		for (int i = 0; i < 1000; ++i)
		{
			my_mutex_1.lock();
			std::cout << "向队列中添加一个元素" << std::endl;
			my_deque.push_back(i);
			my_mutex_1.unlock();
		}
	}
	void ReadFunction()
	{
		for (int i = 0; i < 1000; ++i)
		{
			my_mutex_1.lock();
			if(!my_deque.empty())
			{
				std::cout << "读出队列的第一个元素: " << my_deque.front() << std::endl;
				my_deque.pop_front();
			}
			my_mutex_1.unlock();
		}
	}
private:
	std::deque my_deque;
	std::mutex my_mutex_1;

};
int main()
{
	A a;
	std::thread my_thread_1(&A::WriteFunction, std::ref(a));

	std::thread my_thread_2(&A::ReadFunction, std::ref(a));

	my_thread_1.join();
	my_thread_2.join();
    std::cout << "Hello World!\n";
}

上面这段代码,我们运用了互斥量和锁的方法,解决了上面的问题。

注意事项:

lock()和unlock()必须同时成对出现,不可以多写,也不可以少写,要不认会出现不知名的错误。

std::lock_guard() 

这是一个类模板,具体的用法可以看下面的代码。

这个类模板的作用就是替代lock()和unlock()。lock()和unlock()必须同时出现,而std::lock_guard() 只需要出现一个就行。

先来看代码:


#include 
#include 
#include 
#include 

class A
{
public:
	void WriteFunction()
	{
		for (int i = 0; i < 1000; ++i)
		{
			std::lock_guard my_lock_guard(my_mutex_1);
			std::cout << "向队列中添加一个元素" << std::endl;
			my_deque.push_back(i);
		}
	}
	void ReadFunction()
	{
		for (int i = 0; i < 1000; ++i)
		{
			{//其实这个大括号不用写,因为这里恰巧有for循环的大括号
				std::lock_guard my_lock_guard(my_mutex_1);
				if (!my_deque.empty())
				{
					std::cout << "读出队列的第一个元素: " << my_deque.front() << std::endl;
					my_deque.pop_front();
				}

			}
			
		}
	}
private:
	std::deque my_deque;
	std::mutex my_mutex_1;

};
int main()
{
	A a;
	std::thread my_thread_1(&A::WriteFunction, std::ref(a));

	std::thread my_thread_2(&A::ReadFunction, std::ref(a));

	my_thread_1.join();
	my_thread_2.join();
    std::cout << "Hello World!\n";
}

上面的代码我们用std::lock_guard my_lock_guard(my_mutex_1);这句代码代替了lock()和unlock().

我一般这样使用:将需要保护的代码段用一个大括号括起来,然后在大括号括起来的代码的第一句定义std::lock_guard()。

这样做的原理:std::lock_guard my_lock_guard(my_mutex_1);我们定义的std::lock_guard()对象只在这个大括号的作用域内有效。当进入这个作用域时,我们会构造std::lock_guard()对象,在std::lock_guard()的构造函数中其实是执行了lock()这个方法,当退出这个作用域的时候,std::lock_guard()对象将会被析构,而在std::lock_guard()的析构函数中实际是调用了unlock()。这样的操作十分巧妙。

你可能感兴趣的:(多线程c++实现)