c++ 多线程(2):共享数据的保护lock_guard、unique_lock

主要针对多个线程之间共享数据展开的一些讨论:

1.多个线程之间针对共享数据进行读或者写,需要通过信号量进行加锁进行操作。即lock(),unlock()

2.这一对操作跟指针的使用有着某种异曲同工之妙,因此诞生了一个lock_guard()的类模板,可以帮你自动管理unlock(),查看源代码如图:

(1)与智能指针一样,这个lock_guard 就是个类,然后在你用std::mutex变量作为参数进行构造时就会自动调用这个变量的lock()
(2)之后在析构时就会自动调用unlock()
(3)可以看出你可以在生成一个lock_guard 的对象时除了传入mutex还可以传入第二个参数,这个参数是std::adopt_lock,这是std::adopt_lock_t这个标记类的常量对象。传入这个参数后从源码得知不会调用lock(),你得自己调用。

template <class _Mutex>
class _NODISCARD 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 {
        _MyMutex.unlock();
    }

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

private:
    _Mutex& _MyMutex;
};

3.当然有另一个类模板可以完全包含std::lock_guard的功能而且更加的灵活,他就是unique_ptr…的兄弟unique_lock,哈哈开玩笑,感觉没啥关系把,长得差不多。它的第二个参数除了可以有std::adopt_lock,还可以有std::try_to_lock,和std::defer_lock:

(1)使用std::adopt_lock之前信号量必须要lock(),这句话是正确的当且仅当用unique_lock的时候,用std::lock_guard的时候可没有这个必须条件,查看源码便知:可以看出lock_guard仅仅就是说“我不管啊,你用了std::adopt_lock我可就不给你lock了”,而unique_lock虽然也说“我不管啊,你用了std::adopt_lock我可就不给你lock了”但是它不放心啊,它要求你在用std::adopt_lock之前必须确认mutex已经被lock了,因为它会把_Owns置成true,意思是拥有了这个信号量的锁,从而assume already locked。

//lock_guard的相应构造函数
lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) {}
//unique_lock的相应构造函数
 _NODISCARD_CTOR unique_lock(_Mutex& _Mtx, adopt_lock_t)
        : _Pmtx(_STD addressof(_Mtx)), _Owns(true) {}

(2)使用std::try_to_lock时要确保信号量没有lock,这一点与之前的std::adopt_lock是相反的。这是因为在使用try_to_lock的时候还会去进行lock,如果你之前已经lock过了,就会阻塞在这里。源码如下:

_NODISCARD_CTOR unique_lock(_Mutex& _Mtx, try_to_lock_t)
        : _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock()) {}

(3)使用std::defer_lock前也是要确保信号量是没有lock的,最后想借用cppreference的一句话来总结一下这三个:
小结

4.unique_lock是个类,所以它是有一些常用的成员函数的,如

(1)lock()
(2)unlock()
什么时候会用这个lock呢?当使用这个std::defer_lock参数的时候,就可以在其之后用lock了来自由的控制什么时候对这个信号量上锁
什么时候用unlock呢?不是说这个智能锁的作用就是为了自动unlock吗,当然是的,但是有时候可以中途使用unlock解除锁从而释放它让别的地方使用,减小临界区的大小。你也不用担心自己的unlock会对unique_lock最后的析构中的unlock产生什么影响?人家给你unlock前会判断的
(3)try_lock()尝试去加锁,加不上就返回false,而不是阻塞
(4)release()解除unique_lock对象与信号量的绑定,并返回执行此信号量的指针,例如这种有点弱智的代码:(q_mag_mutex这个信号量保护了q_msg这个队列)

std::unique_lock<std::mutex> my_guard(q_mag_mutex,std::defer_lock);
std::mutex* pmutex = my_guard.release();
pmutex->lock();
q_msg.push(i);
pmutex->unlock();

(5)关于unique_lock的拷贝与赋值与unique_ptr一样,都只支持移动拷贝和移动赋值,因此:
①可以直接用std::move()转右值后拷贝转移unique_lock的所有权
②函数返回的临时变量直接赋值转移unique_lock的所有权:因为直接从函数返回的临时变量(非引用)是右值!例如下图所示:

std::unique_lock<std::mutex> change()
{
	std::unique_lock<std::mutex> temp_guard(q_mag_mutex);
	return temp_guard;
}
void receive_msg()
{
	for (int i = 0; i < 100; i++)
	{
		std::unique_lock<std::mutex> my_guard = change();
		std::mutex* pmutex = my_guard.release();
		q_msg.push(i);
		pmutex->unlock();
		std::cout << "User's id:" << i << "has been inserted" << std::endl;
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值