【C++11多线程入门教程】系列之互斥量lock_guard

互斥量lock_guard

  前面一片博文【C++11多线程入门教程】系列之互斥量mutex介绍了互斥锁mutex的基本用法与注意事项。但是,使用std::mutex时候会出现这样一种情况:

std::mutex mtx;

mtx.lock();
// do_something ......
mtx.unlock();

  如果在mtx.lock()时候,执行do_something出现异常,那么将无法执行mtx.unlock()操作,导致程序卡住。那么,这个时候该怎么办呢?

  这个时候我们介绍lock_guard()类模板来解决这个问题,先看下std::lock_guard()类模板的声明:

// CLASS TEMPLATE lock_guard
template <class _Mutex>
class lock_guard { // class with destructor that unlocks a mutex
public:
    using mutex_type = _Mutex;

    explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock
        _MyMutex.lock();
    }

    lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) { // construct but don't lock
    }

    ~lock_guard() noexcept { // unlock
        _MyMutex.unlock();
    }

    lock_guard(const lock_guard&) = delete;
    lock_guard& operator=(const lock_guard&) = delete;

private:
    _Mutex& _MyMutex;
};

  通过上面的声明可以清晰的看到,lock_guard类模板有两个构造函数(第一个构造函数在构造就上锁,另外一个并不上锁),拷贝构造函数与拷贝运算符禁止使用。同时,我们发现lock_guard类模板在构造函数里面进行lock()上锁,析构函数里面进行unlock()解锁。通过这样的机制,就算执行lock()之后代码区域产生异常,但是执行析构函数时候会自动unlock()解锁。不会导致卡死的情况发生。

std::lock_guard代码示例

  下面的示例是错误代码,运行会导致卡住。如果需要运行正确,只需要将②注释掉,更换使用①。

#include <iostream>
#include <thread>
#include <mutex>
#include <stdexcept>

std::mutex mtx;

void print_even(int x)
{
	if (0 == x % 2)
	{
		std::cout << x << " is even." << std::endl;
	}
	else
	{
		throw (std::logic_error("not even"));
	}
}

void print_thread_id(int id)
{
	try
	{
		//std::lock_guard<std::mutex> lck(mtx); // ①
		mtx.lock(); // ②
		print_even(id);
		mtx.unlock(); // ③
	}
	catch (std::logic_error&)
	{
		std::cout << "[exception caught]" << std::endl;
	}
}

int main(void)
{
	std::thread threads[10];

	for (int i = 0; i < 10; ++i)
	{
		threads[i] = std::thread(print_thread_id, i + 1);
	}
	for (auto& th : threads)
		th.join();

	system("pause");
	return 0;
}

  关于lock_guard()能够有效处理mutex的lock()上锁到unlock()之间的出现异常不会卡住的问题,那么lock_guard()是否能够解决死锁问题呢?明显是不可以的。我们看下面的测试代码:

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

class Tickets
{
public:
	// 订票线程
	void book_tickets()
	{
		for (int i = 0; i < 1000000; i++)
		{
			std::cout << "订票开始,该线程执行:" << i << std::endl;
			//mtx_tickets_0.lock();  // ①
			//mtx_tickets_1.lock();  // ①
			std::lock_guard<std::mutex> lck0(mtx_tickets_0); 
			std::lock_guard<std::mutex> lck1(mtx_tickets_1); 
			tickets_.push_back(i);
			//mtx_tickets_0.unlock(); // ②
			//mtx_tickets_1.unlock(); // ②
		}
		std::cout << "订票线程执行完毕." << std::endl;
	}
	// 出票线程
	void issue_tickets()
	{
		for (int i = 0; i < 1000000; i++)
		{
			if (!tickets_.empty()) // 订票量不为空
			{
				std::lock_guard<std::mutex> lck1(mtx_tickets_1);  /  ⑧
				std::lock_guard<std::mutex> lck0(mtx_tickets_0);  /
				//mtx_tickets_1.lock();  // ③
				//mtx_tickets_0.lock();  // ③
				int ticket = tickets_.front();
				tickets_.pop_front();
				//mtx_tickets_1.unlock();  // ④
				//mtx_tickets_0.unlock();  // ④
			}
			else
			{
				std::cout << "订票队列里面为空:" << i << std::endl;
			}
		}
		std::cout << "出票线程执行完毕." << std::endl;
	}
private:
	std::list<int> tickets_;
	std::mutex mtx_tickets_0;
	std::mutex mtx_tickets_1;
};

int main(void)
{
	Tickets tickets;

	std::thread bookTickets(&Tickets::book_tickets, &tickets);
	std::thread issueTickets(&Tickets::issue_tickets, &tickets);

	bookTickets.join();
	issueTickets.join();

	std::cout << "主线程执行结束." << std::endl;

	system("pause");
	return 0;
}

  上述代码是我们上一篇博文的订票与出票的mutex示例程序,我们换用lock_guard()来进行操作,发现还是会出现死锁卡住原因。其实不难理解:lock_guard()只是包装了mutex的lock()与unlock()分别位置构造与析构函数中。但是,如果上锁顺序不一致,仍然会出现死锁现象。解决方法是将代码中的⑦⑧上锁顺序一致即可。

小结

关于lock_guard()的一些小结:

  • lock_guard()包装了mutex的lock()与unlock()机制,避免我们使用时候忘记unlock()的情况。当然,使用灵活性没有mutex强。
  • lock_guard()的上锁机制能够有效避免lock()上锁后异常发生在unlock()解锁前的卡死现象。
  • lock_guard()还提供了一个构造但是不上锁的构造函数,通过下面的写法也能够避免忘记unlock()导致的异常。
mtx.lock();
std::lock_guard<std::mutex> lck(mtx, std::adopt_lock());
// do_something...

// 这里不需要unlock()操作,lock_guard()会帮助析构mtx
  • lock_guard()虽然避免忘记unlock()的风险,但是只能够在离开作用于使用析构函数进行unlock()。没有单独的接口进行lock()与unlock(),不够灵活。
参考

http://www.cplusplus.com/reference/mutex/lock_guard/

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值