C++lock_guard与unique_lock 的使用

一、定义

C++11中提供了std::mutex互斥量,共包含四种类型:

  • std::mutex:最基本的mutex类。
  • std::recursive_mutex:递归mutex类,能多次锁定而不死锁。
  • std::time_mutex:定时mutex类,可以锁定一定的时间。
  • std::recursive_timed_mutex:定时递归mutex类。

另外,还提供了两种锁类型: 

  • std::lock_guard:方便线程对互斥量上锁。
  • std::unique_lock:方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。

相关函数

  • std::try_lock:尝试同时对多个互斥量上锁。
  • std::lock:可以同时对多个互斥量上锁。
  • std::call_once:如果多个线程需要同时调用某个函数,call_once可以保证多个线程对该函数只调用一次。
     

std::lock_guard自动释放锁,它是一个模板类,模板类型可以是以上的四种锁,用于自动锁定解锁,直到对象作用域结束。其原理是:声明一个局部的lock_guard对象,在其构造函数中进行加锁,在其析构函数中进行解锁。最终的结果就是:在定义该局部对象的时候加锁(调用构造函数),出了该对象作用域的时候解锁(调用析构函数)

二、实例

使用方法
1.首先需要包含mutex头文件
2.然后创建一个锁 std::mutex mutex
3.在需要被加锁的作用域内 将mutex传入到创建的std::lock_guard局部对象中

 举个例子



std::mutex mtx;

int main()
{
	{
		std::lock_guard<std::mutex> guard(mtx);
		std::cout << "互斥量被占用"<< mtx.try_lock() << std::endl;  // 0,互斥量被占用
	}

	std::cout <<"互斥量被释放" <<mtx.try_lock() << std::endl;     // 1,互斥量释放 
	mtx.unlock();
        return 0;
}

 

std::try_lock支持尝试对多个互斥量进行锁定,尝试锁定成功返回-1,否则返回锁定失败的互斥量的位置,例如第一个锁定失败返回0、第二个失败返回1。

int main ()
{
    std::mutex mtx1;
    std::mutex mtx2;

    if (-1 == std::try_lock(mtx1, mtx2))
    {
        std::cout << "locked" << std::endl;
        mtx1.unlock();
        mtx2.unlock();
    }

    return 0;
}

三、避免死锁

假设有两个mutex(m1、m2),一个线程先锁住m1再锁住m2,另一个线程先锁住m2,再锁住m1,就有可能会出现死锁。std::lock内部使用了死锁避免的算法,可以有效避免死锁


std::lock(m1, m2)

std::lock_guard lock1(m1, std::adopt_lock)

std::lock_guard lock2(m2, std::adopt_lock)

std::lock支持对多个锁锁定,并且避免死锁的出现,以下代码运行时有可能出现死锁的情况:

void func(std::mutex* mtx1, std::mutex* mtx2, int index)
{
    std::lock_guard<std::mutex> lock1(*mtx1,std::adopt_lock);
    std::lock_guard<std::mutex> lock2(*mtx2,std::adopt_lock);

    std::cout << index << "out\n";
}

int main ()
{
    std::mutex mtx1;
    std::mutex mtx2;

    // 两个线程的互斥量锁定顺序不同,可能造成死锁
    std::thread t1(func, &mtx1, &mtx2, 1);
    std::thread t2(func, &mtx2, &mtx1, 2);

    t1.join();
    t2.join();

    return 0;
}

而用std::lock能避免多个锁出现死锁


void func(std::mutex* mtx1, std::mutex* mtx2, int index)
{
	 // std::lock_guard只是保证互斥量在作用域结束时被释放
	std::lock(*mtx1, *mtx2); // 同时锁定
    std::lock_guard<std::mutex> lock1(*mtx1, std::adopt_lock);
	std::lock_guard<std::mutex> lock2(*mtx2, std::adopt_lock);

	// 等价方法:
	/*std::unique_lock<std::mutex> lock1(*mtx1, std::defer_lock);
	std::unique_lock<std::mutex> lock2(*mtx2, std::defer_lock);
	std::lock(lock1, lock2);*/

	std::cout << index << "out\n";
}

int main()
{
	std::mutex mtx1;
	std::mutex mtx2;

	std::thread t1(func, &mtx1, &mtx2, 1);
	std::thread t2(func, &mtx2, &mtx1, 2);

	t1.join();
	t2.join();
        return 0;
}

三个窗口卖100张票

 std::mutex mtx; // 全局的一把互斥锁
 int ticketCount = 100;;

 void sellTicket(int index)
 {
	 while (ticketCount > 0) // ticketCount=1  锁+双重判断
	 {
		 // 保证所有线程都能释放锁,防止死锁问题的发生 scoped_ptr
		 lock_guard<std::mutex> lock(mtx);
		 if (ticketCount > 0)
		 {
			 // 临界区代码段  =》  原子操作 =》 线程间互斥操作了 =》 mutex
			 cout << "窗口:" << index << "卖出第:" << ticketCount << "张票!" << endl;
			 //cout << ticketCount << endl;
			 ticketCount--;
		 }
		 std::this_thread::sleep_for(std::chrono::milliseconds(100));
	 }
 }


 int main()
 {
	 list<std::thread> tlist;
	 for (int i = 0; i < 3; ++i)
	 {
		 tlist.push_back(std::thread(sellTicket, i));
	 }


	 for (std::thread &t : tlist)
	 {
		 t.join();
	 }

	 cout << "所有窗口卖票结束!" << endl;
}

 

四、unique_lock 

简单地讲,unique_lock 是 lock_guard 的升级加强版,它具有 lock_guard 的所有功能,同时又具有其他很多方法,使用起来更强灵活方便,能够应对更复杂的锁定需要。

特点如下:

  • 创建时可以不锁定(通过指定第二个参数为std::defer_lock),而在需要时再锁定
  • 可以随时加锁解锁
  • 作用域规则同 lock_grard,析构时自动释放锁
  • 不可复制,可移动
  • 条件变量需要该类型的锁作为参数(此时必须使用unique_lock)
#include <mutex>
#include <thread>
#include <chrono>
 
struct Box {
    explicit Box(int num) : num_things{num} {}
 
    int num_things;
    std::mutex m;
};
 
void transfer(Box &from, Box &to, int num)
{
    // don't actually take the locks yet
    std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
    std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);
 
    // lock both unique_locks without deadlock
    std::lock(lock1, lock2);
 
    from.num_things -= num;
    to.num_things += num;
    std::cout << from.num_things << "  out\n";
    std::cout << to.num_things << "  out\n";
	// 'from.m' and 'to.m' mutexes unlocked in 'unique_lock' dtors
}
 
int main()
{
    Box acc1(100);
    Box acc2(50);
 
    std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10);
    std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5);
 
    t1.join();
    t2.join();
}

参考:
《深入应用C++11》笔记-互斥量std::mutex、锁std::lock_guard_WizardtoH的博客-CSDN博客_mutex_lock

c++11中的lock_guard和unique_lock使用浅析_guotianqing的博客-CSDN博客_lock_guard和unique_lock

std::lock的使用方法_tillmanSkate的博客-CSDN博客_std::lock

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
lock_guardunique_lockC++11中的两种线程安全锁定方式,它们都是用来保护共享资源的,但它们的实现方式略有不同。 lock_guard源码: ```c++ template<typename _Mutex> class lock_guard { public: typedef _Mutex mutex_type; //构造函数,锁定互斥量 explicit lock_guard(mutex_type& __m) : _M_device(__m) { _M_device.lock(); } //析构函数,释放互斥量 ~lock_guard() { _M_device.unlock(); } //禁止拷贝构造函数和赋值操作符 lock_guard(const lock_guard&) = delete; lock_guard& operator=(const lock_guard&) = delete; private: mutex_type& _M_device; }; ``` 在lock_guard的构造函数中,它会锁定传入的互斥量。在lock_guard的析构函数中,它会释放互斥量。这样,在lock_guard对象的生命周期中,只要它存在,它所锁定的互斥量就不会被其他线程所访问。 unique_lock源码: ```c++ template<typename _Mutex> class unique_lock { public: typedef _Mutex mutex_type; //构造函数 unique_lock() noexcept : _M_device(0), _M_owns(false) { } explicit unique_lock(mutex_type& __m) noexcept : _M_device(std::addressof(__m)), _M_owns(true) { _M_device->lock(); } unique_lock(mutex_type& __m, defer_lock_t) noexcept : _M_device(std::addressof(__m)), _M_owns(false) { } unique_lock(mutex_type& __m, adopt_lock_t) noexcept : _M_device(std::addressof(__m)), _M_owns(true) { } template<typename _Clock, typename _Duration> unique_lock(mutex_type& __m, const chrono::time_point<_Clock, _Duration>& __t) : _M_device(std::addressof(__m)), _M_owns(false) { const auto __now = _Clock::now(); if (__now < __t) { const auto __d = chrono::duration_cast<chrono::milliseconds>(__t - __now); if (_M_device->try_lock_for(__d)) _M_owns = true; } } template<typename _Rep, typename _Period> unique_lock(mutex_type& __m, const chrono::duration<_Rep, _Period>& __d) : _M_device(std::addressof(__m)), _M_owns(false) { if (_M_device->try_lock_for(__d)) _M_owns = true; } //析构函数 ~unique_lock() noexcept { if (_M_owns) _M_device->unlock(); } //加锁 void lock() { if (!_M_owns) { _M_device->lock(); _M_owns = true; } } //尝试加锁 bool try_lock() { if (!_M_owns && _M_device->try_lock()) { _M_owns = true; return true; } return false; } //解锁 void unlock() { if (_M_owns) { _M_device->unlock(); _M_owns = false; } } //释放锁定的互斥量 mutex_type* release() noexcept { mutex_type* __t = _M_device; _M_device = 0; _M_owns = false; return __t; } //获取锁定的互斥量 mutex_type* mutex() const noexcept { return _M_device; } //获取锁定状态 bool owns_lock() const noexcept { return _M_owns; } //禁止拷贝构造函数和赋值操作符 unique_lock(const unique_lock&) = delete; unique_lock& operator=(const unique_lock&) = delete; private: mutex_type* _M_device; bool _M_owns; }; ``` unique_lock相对于lock_guard有更多的构造函数,可以更灵活地操作互斥量。unique_lock的默认构造函数和defer_lock_t构造函数不会锁定互斥量,而adopt_lock_t构造函数会假设当前线程已经锁定互斥量,直接使用unique_lock还提供了try_lock、unlock、release等成员函数,可以更方便地操作互斥量。 总之,lock_guardunique_lock的实现方式不同,但它们都可以用来保护共享资源的线程安全。在实际使用中,需要根据需求灵活选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值