c++11多线程中的互斥量

写在前面

在多线程程序中互斥量的概念十分重要,要保护线程之间的共享数据,互斥量的lock、unlock、try_lock函数,以及类模板lock_guard、unique_lock十分重要

栗子

首先先看一下,没有再共享数据上做任何保护的程序:

#include <iostream>
#include <string>
#include <thread>
#include <list>
#include <mutex>

class MesProcess
{
public:
	void Inmsglist()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::cout << "Inmsglist线程正在执行,插入数字为:" << i << std::endl;
			msglist.push_back(i);
		}
	}

	void getcommand(int &command)
	{
		if (!msglist.empty())
		{
			command = msglist.front();
			msglist.pop_front();
			std::cout << "Outmsglist线程正在执行,取出数字为:" << command << std::endl;
		}
		else
		{
			std::cout << "Outmsglist线程正在执行,但是消息队列为空" << std::endl;
		}
	}
	void Outmsglist()
	{
		int command;
		for (int i = 0; i < 10000; i++)
		{
			getcommand(command);
		}
	}
private:
	std::list<int> msglist;
};

int main()
{
	std::cout << "主线程的线程id为: " << std::this_thread::get_id() << std::endl;

	MesProcess mpobj;
	std::thread Outmsg_thread(&MesProcess::Outmsglist, &mpobj);
	std::thread Inmsg_thread(&MesProcess::Inmsglist, &mpobj);

	Inmsg_thread.join();
	Outmsg_thread.join();

	std::cout << "主线程运行结束" << std::endl;
	return 0;
}

因为程序中没有对共享数据msglist进行保护,所以,不止到在什么时候就会产生异常,程序十分不稳定,我们很具以上程序增加对于共享数据的保护,首先说一下,mutex类

A mutex is a lockable object that is designed to signal when critical sections of code need exclusive access, preventing other threads with the same protection from executing concurrently and access the same memory locations.

//互斥锁是一个可锁定的对象,当代码的关键部分需要独占访问时,互斥锁会发出信号,防止具有相同保护的其他线程并发执行并访问相同的内存位置。

mutex成员函数

(constructor)

mutex类的构造函数
 lock 锁定互斥锁
try_lock 尝试锁定互斥锁,马上返回,如果锁定成功,返回true,否则返回false
unlock 解锁互斥锁
native_handle

返回底层实现定义的原生句柄 

 修改后的程序为:

#include <iostream>
#include <string>
#include <thread>
#include <list>
#include <mutex>

class MesProcess
{
public:
	void Inmsglist()
	{
		for (int i = 0; i < 10000; i++)
		{
			my_mutex.lock();
			std::cout << "Inmsglist线程正在执行,插入数字为:" << i << std::endl;
			msglist.push_back(i);
			my_mutex.unlock();
		}
	}

	void getcommand(int &command)
	{
		if (!msglist.empty())
		{
			command = msglist.front();
			msglist.pop_front();
			std::cout << "Outmsglist线程正在执行,取出数字为:" << command << std::endl;
		}
		else
		{
			std::cout << "Outmsglist线程正在执行,但是消息队列为空" << std::endl;
		}
	}
	void Outmsglist()
	{
		int command;
		for (int i = 0; i < 10000; i++)
		{
			my_mutex.lock();
			getcommand(command);
			my_mutex.unlock();
		}
	}
private:
	std::list<int> msglist;
	std::mutex my_mutex;
};

int main()
{
	std::cout << "主线程的线程id为: " << std::this_thread::get_id() << std::endl;

	MesProcess mpobj;
	std::thread Outmsg_thread(&MesProcess::Outmsglist, &mpobj);
	std::thread Inmsg_thread(&MesProcess::Inmsglist, &mpobj);

	Inmsg_thread.join();
	Outmsg_thread.join();

	std::cout << "主线程运行结束" << std::endl;
	return 0;
}

其中lock和unlock函数必须是成对出现的,如果忘记unlock函数没有写,将会操作程序发生死锁,无法继续运行,在这里c++11又提出一个新的封装器,运用RAll机制的lock_guard封装器

在构造lock_guard对象的时候,即给互斥锁上锁(这里可以提供std::adopt_lock参数来避免构造函数调用lock参数),在析构函数中调用unlock函数,即创建 lock_guard对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard对象的作用域时,销毁 lock_guard 并释放互斥。

#include <iostream>
#include <string>
#include <thread>
#include <list>
#include <mutex>

class MesProcess
{
public:
	void Inmsglist()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::lock_guard<std::mutex> gulock(my_mutex);
			//my_mutex.lock();
			std::cout << "Inmsglist线程正在执行,插入数字为:" << i << std::endl;
			msglist.push_back(i);
			//my_mutex.unlock();
		}
	}

	void getcommand(int &command)
	{
		if (!msglist.empty())
		{
			command = msglist.front();
			msglist.pop_front();
			std::cout << "Outmsglist线程正在执行,取出数字为:" << command << std::endl;
		}
		else
		{
			std::cout << "Outmsglist线程正在执行,但是消息队列为空" << std::endl;
		}
	}
	void Outmsglist()
	{
		int command;
		for (int i = 0; i < 10000; i++)
		{
			my_mutex.lock();   //这里已经给my_mutex上锁,所以在lock_guard对象构造的时候,要避免再次调用lock函数,需要提供std::adopt_lock参数
			std::lock_guard<std::mutex> gulock(my_mutex, std::adopt_lock);
			
			getcommand(command);
			//my_mutex.unlock();
		}
	}
private:
	std::list<int> msglist;
	std::mutex my_mutex;
};

int main()
{
	std::cout << "主线程的线程id为: " << std::this_thread::get_id() << std::endl;

	MesProcess mpobj;
	std::thread Outmsg_thread(&MesProcess::Outmsglist, &mpobj);
	std::thread Inmsg_thread(&MesProcess::Inmsglist, &mpobj);

	Inmsg_thread.join();
	Outmsg_thread.join();

	std::cout << "主线程运行结束" << std::endl;
	return 0;
}	

既然已经提到了lock_guard封装器,就不得不说一下,unique_lock封装器,相对于lock_guard封装器来说,unique_lock功能更强大,并且使用更灵活

除了在lock_guard中提到的std::adopt_lock之外,在unique_lock中还有std::defer_lock,std::try_to_lock

表示构造unique_lock对象并且调用lock函数
std::adopt_lock 构造unique_lock对象,默认互斥锁已经使用了lock
std::defer_lock 构造unique_lock对象,不调用lock函数,并且此时互斥锁并未调用lock函数
std::try_to_lock 构造unique_lock对象,并且尝试对互斥锁调用lock函数,如果没有锁定成功,也会立即返回,不会发生阻塞,并且此时互斥锁并未调用lock函数

使用std::try_to_lock,我们可以改写Inmsglist成员函数:

void Inmsglist()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> ullock(my_mutex, std::try_to_lock);
			//if (bool(ullock))    //返回尝试锁定是否成功
			if(ullock.owns_lock())
			{
				std::cout << "Inmsglist线程正在执行,插入数字为:" << i << std::endl;
				msglist.push_back(i);
			}
			else
			{
				std::cout << "Inmsglist线程正在执行,但是尝试得到互斥锁,没有成功" << i << std::endl;
			}

		}
	}

除了owns_lock可以测试锁是否占有其关联互斥 ,在unique_lock中还重载了bool转换函数,但是前面有explicit修饰,所以只能进行显示转换操作,所以上面也可以使用bool(ullock)测试锁是否占有关联互斥。

使用std::adopt_lock,我们可以改写Inmsglist成员函数:

void Inmsglist()
	{
		for (int i = 0; i < 10000; i++)
		{
			my_mutex.lock();     //使用std::adopy_lock必须保障主动上锁
			std::unique_lock<std::mutex> ullock(my_mutex, std::adopt_lock);
			std::cout << "Inmsglist线程正在执行,插入数字为:" << i << std::endl;
			msglist.push_back(i);
		}
	}

在数据库中,事务之间放置发生死锁的方法之一是:在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。

我们也可以采用这样的策略进行编程,但是这样就要求我们必须要求有多个互斥量的存在

//假设函数中存在两个互斥量,为别为my_mutex1,my_mutex2
	void Inmsglist()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> ullock1(my_mutex1, std::adopt_lock);
			std::unique_lock<std::mutex> ullock1(my_mutex2, std::adopt_lock);

			std::lock(my_mutex1, my_mutex2);   //只有所有互斥量都上锁,才可以向后执行,否则只要存在一个未上锁,就会导致阻塞
			std::cout << "Inmsglist线程正在执行,插入数字为:" << i << std::endl;
			msglist.push_back(i);
		}
	}

unique_lock的重要成员函数

lock() 锁定关联互斥 
unlock() 解锁关联互斥 
try_lock() 尝试锁定关联互斥,若互斥不可用则返回 ,可以由owns_lock以及bool()显示转换方式查看是否锁定关联互斥
try_lock_for() 试图锁定关联的定时可锁 (TimedLockable) 互斥,若互斥在给定时长中不可用则返回 
try_lock_until() 尝试锁定关联可定时锁 (TimedLockable) 互斥,若抵达指定时间点互斥仍不可用则返回 
swap() 与另一 std::unique_lock 交换状态 
release() 将关联互斥解关联而不解锁它 

 栗子

void Inmsglist()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> ullock(my_mutex, std::defer_lock);   //构造函数中没有进行加锁
			//ullock.lock();   //自己增加
			//ullock.unlock();  //自己解锁
			/*ullock.lock();
			std::mutex *mut = ullock.release();
			std::cout << "Inmsglist线程正在执行,插入数字为:" << i << std::endl;
			msglist.push_back(i);
			mut->unlock();*/
			
			if (ullock.try_lock())   //具有返回值的try_lock函数
			{
				std::cout << "Inmsglist线程正在执行,插入数字为:" << i << std::endl;
				msglist.push_back(i);
			}
			else
			{
				std::cout << "Inmsglist线程正在执行,但是没有拿到锁:" << i << std::endl;
			}
			
		}
	}

release函数原型为:

_Mutex *release() _NOEXCEPT        //返回管理的mutex对象指针,并释放所有权,使用之后mutex和unique_lock不再有关系
		{	// disconnect
		_Mutex *_Res = _Pmtx;
		_Pmtx = 0;
		_Owns = false;
		return (_Res);
		}

参考文献

http://www.cplusplus.com/reference

《C++11并发与多线程视频课程》 视频地址:http://edu.51cto.com/course/15287.html

猜你喜欢

转载自blog.csdn.net/li1615882553/article/details/85530621