【C++11多线程入门教程】系列之unique_lock

unique_lock功能

  上一篇博文中我们介绍std::lock_guard()互斥量函数,一般我们使用lock_guard()取代std::mutex的使用,为了避免忘记unlock()情况的出现。此篇博文我们介绍一下关于std::unque_lock()互斥量的使用方法,unique_lock()lock_guard()功能更加全面一些,不只是像lock_guard()在构造/析构函数中lock()unlock()函数。同时unique_lock()还提供多种初始化的方式std::adopt_lockstd::defer_lockstd::try_to_lock多个不同参数模式。具体下面会详细介绍,我们先来看一下unique_lock类的相关构造函数与成员函数的源码。

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

    // CONSTRUCT, ASSIGN, AND DESTROY
    unique_lock() noexcept : _Pmtx(nullptr), _Owns(false) {}
	// 显示默认构造函数,构造unique_lock同时也上锁
    explicit unique_lock(_Mutex& _Mtx) : _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // construct and lock
        _Pmtx->lock();
        _Owns = true;
    }
	// 构造函数,第二个参数adopt_lock代表输入进去的互斥量_Mtx之前已经上锁了
    unique_lock(_Mutex& _Mtx, adopt_lock_t)
        : _Pmtx(_STD addressof(_Mtx)), _Owns(true) { // construct and assume already locked
    }
	// 构造函数,第二个参数defer_lock代表输入进去的互斥量_Mtx没有上锁,后面会手动上锁
    unique_lock(_Mutex& _Mtx, defer_lock_t) noexcept
        : _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // construct but don't lock
    }
	// 构造函数,第二个参数try_to_lock代表输入进去的互斥量_Mtx没有上锁,并且尝试给_Mtx上锁
    unique_lock(_Mutex& _Mtx, try_to_lock_t)
        : _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock()) { // construct and try to lock
    }
	// 构造函数,第二个参数代表给_Mtx上锁需要的时间范围内要完成上锁,不然就没有锁成功
    template <class _Rep, class _Period>
    unique_lock(_Mutex& _Mtx, const chrono::duration<_Rep, _Period>& _Rel_time)
        : _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock_for(_Rel_time)) { // construct and lock with timeout
    }
	// 构造函数,第二个函数代表在绝对某个时间点之前是否上锁成功or失败
    template <class _Clock, class _Duration>
    unique_lock(_Mutex& _Mtx, const chrono::time_point<_Clock, _Duration>& _Abs_time)
        : _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock_until(_Abs_time)) { // construct and lock with timeout
    }

    unique_lock(_Mutex& _Mtx, const xtime* _Abs_time)
        : _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // try to lock until _Abs_time
        _Owns = _Pmtx->try_lock_until(_Abs_time);
    }
	// 移动构造函数,获取之前的unique_lock的所有权
    unique_lock(unique_lock&& _Other) noexcept : _Pmtx(_Other._Pmtx), _Owns(_Other._Owns) { // destructive copy
        _Other._Pmtx = nullptr;
        _Other._Owns = false;
    }
	// 移动运算符,获取之前unique_lock的所有权
    unique_lock& operator=(unique_lock&& _Other) { // destructive copy
        if (this != _STD addressof(_Other)) { // different, move contents
            if (_Owns) {
                _Pmtx->unlock();
            }

            _Pmtx        = _Other._Pmtx;
            _Owns        = _Other._Owns;
            _Other._Pmtx = nullptr;
            _Other._Owns = false;
        }
        return *this;
    }
	// 析构函数
    ~unique_lock() noexcept { // clean up
        if (_Owns) {
            _Pmtx->unlock();
        }
    }
	// 默认不可以使用拷贝构造函数与拷贝运算符
    unique_lock(const unique_lock&) = delete;
    unique_lock& operator=(const unique_lock&) = delete;
	// unique_lock类成员函数lock
    void lock() { // lock the mutex
        _Validate();
        _Pmtx->lock();
        _Owns = true;
    }
	// unique_lock类成员函数try_lock
    _NODISCARD bool try_lock() { // try to lock the mutex
        _Validate();
        _Owns = _Pmtx->try_lock();
        return _Owns;
    }
	// unique_lock类成员函数try_lock_for
    template <class _Rep, class _Period>
    _NODISCARD bool try_lock_for(const chrono::duration<_Rep, _Period>& _Rel_time) { // try to lock mutex for _Rel_time
        _Validate();
        _Owns = _Pmtx->try_lock_for(_Rel_time);
        return _Owns;
    }
	// unique_lock类成员函数try_lock_until
    template <class _Clock, class _Duration>
    _NODISCARD bool try_lock_until(
        const chrono::time_point<_Clock, _Duration>& _Abs_time) { // try to lock mutex until _Abs_time
        _Validate();
        _Owns = _Pmtx->try_lock_until(_Abs_time);
        return _Owns;
    }

    _NODISCARD bool try_lock_until(const xtime* _Abs_time) { // try to lock the mutex until _Abs_time
        _Validate();
        _Owns = _Pmtx->try_lock_until(_Abs_time);
        return _Owns;
    }
	// unique_lock类成员函数unlock
    void unlock() { // try to unlock the mutex
        if (!_Pmtx || !_Owns) {
            _Throw_system_error(errc::operation_not_permitted);
        }

        _Pmtx->unlock();
        _Owns = false;
    }
	// unique_lock类成员函数swap交换两个unique_lock所有权
    void swap(unique_lock& _Other) noexcept { // swap with _Other
        _STD swap(_Pmtx, _Other._Pmtx);
        _STD swap(_Owns, _Other._Owns);
    }
	// 
    _Mutex* release() noexcept { // disconnect
        _Mutex* _Res = _Pmtx;
        _Pmtx        = nullptr;
        _Owns        = false;
        return _Res;
    }

    _NODISCARD bool owns_lock() const noexcept { // return true if this object owns the lock
        return _Owns;
    }

    explicit operator bool() const noexcept { // return true if this object owns the lock
        return _Owns;
    }

    _NODISCARD _Mutex* mutex() const noexcept { // return pointer to managed mutex
        return _Pmtx;
    }

private:
    _Mutex* _Pmtx;
    bool _Owns;

    void _Validate() const { // check if the mutex can be locked
        if (!_Pmtx) {
            _Throw_system_error(errc::operation_not_permitted);
        }

        if (_Owns) {
            _Throw_system_error(errc::resource_deadlock_would_occur);
        }
    }
};

  上面C++11多线程源码库中的unique_lock类的源码生命部分,其中主要分为2个功能函数划分:①构造函数的不同功能初始化;②成员函数;

  • 显示默认构造函数,构造同时进行上锁,即构造函数第二个参数没有;
  • 构造函数第二个参数使用std::adopt_lock,即该互斥量在调用unique_lock之前已经完成上锁功能,如果没有上锁那么将会报异常;
  • 构造函数第二个参数使用std::try_to_lock,即该互斥量在调用unique_lock时候尝试对其进行上锁,可能会上锁失败or成功;
  • 构造函数第二个参数使用std::defer_lock,即该互斥量在调用unique_lock时候不对齐进行上锁,在后面使用时候动态的调用lock()或者unlock()函数进行上锁与解锁;
  • 模板构造函数针对在一段时间内or某一个时间点的上锁成功与否的初始化;
  • unique_lock移动构造函数转移所有权;

下面是相关成员函数:

  • 函数try_locktry_lock_fortry_lock_until三个成员函数分别是对互斥量进行上锁,只是后面两个函数一个是在一段时间内是否上锁成功,try_lock_until为绝对某个时间点是否上锁成功;
  • 函数swap为交换两个unique_lock对象的所有权;
  • 函数release为释放unique_lock对象的所有权;

  这里对类unique_lock源码部分的做个小结:主要针对不同的使用需求来进行不同的构造unique_lock对象。关于构造函数的第二个参数,可根据你实际需求来进行输入。我们看到unique_lock功能较lock_guard类更全面,但是可能性能略微低于lock_guard类。unique_lock类使用更为灵活与全面,同时也避免忘记unlock的情况。

代码演示

  下面的示例代码涵盖了不同的构造函数方式,以及相关使用方法和错误使用的提示。每一小段的注释解开即可进行编译查看使用unique_lock类的效果。相关解释使用与解释都在代码中体现,这里就不多余解释了。

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

std::mutex mtx;

void print_block(int n, char c)
{
	///
	//for (int i = 0; i < n; ++i)
	//{
	//	mtx.lock();
	//	std::cout << c;
	//	mtx.unlock();
	//}
	//std::cout << "\n";   // 无序输出

	///
	std::unique_lock<std::mutex> lck(mtx); // 有序
	for (int i = 0; i < n; ++i)
	{
		//std::unique_lock<std::mutex> lck(mtx); // 无序
		std::cout << c;
	}
	std::cout << "\n"; // 有序输出

	///
	//mtx.lock();
	//std::unique_lock<std::mutex> lck(mtx, std::adopt_lock);
	//for (int i = 0; i < n; ++i)
	//{
	//	std::cout << c;
	//}
	//std::cout << "\n"; // 有序输出

	///
	//std::unique_lock<std::mutex> lck(mtx, std::try_to_lock);
	//bool succ = lck.owns_lock();
	//if (succ)
	//{
	//	for (int i = 0; i < n; ++i)
	//	{
	//		std::cout << c;
	//	}
	//	std::cout << "\n";
	//}
	//else
	//{
	//	std::cout << "can not lock succ." << std::endl;
	//}  // 竞争机制,只能输出一个线程的结果,另外一个线程打印没有lock成功

	///
	//std::unique_lock<std::mutex> lck(mtx, std::defer_lock);
	//for (int i = 0; i < n; ++i)
	//{
	//	lck.lock();
	//	std::cout << c;
	//	lck.unlock();
	//}
	//std::cout << "\n"; // 无序输出


	/
	//std::unique_lock<std::mutex> lck(mtx); // 有序
	//std::mutex *pt_rel = lck.release(); // mtx所有权转让给pt_rel,因此需要pt_rel自己手动解锁
	pt_rel->lock(); // error 接收lck.release()无需再次上锁
	//for (int i = 0; i < n; ++i)
	//{
	//	//pt_rel->lock(); // error 接收lck.release()无需再次上锁
	//	std::cout << c;
	//}
	//std::cout << "\n"; // 有序输出
	//pt_rel->unlock();

	///
	//std::unique_lock<std::mutex> lck(mtx); 
	//std::unique_lock<std::mutex> lck_move(std::move(lck)); // 移动构造函数使用,转移所有权
	//for (int i = 0; i < n; ++i)
	//{
	//	std::cout << c;
	//}
	//std::cout << "\n";
}

int main(void)
{
	std::thread th1(print_block, 50, '*');
	std::thread th2(print_block, 50, '$');

	th1.join();
	th2.join();

	return 0;
}
小结

  本篇博文主要介绍一下unique_lock类的使用,其功能相比较lock_guard比较全面,灵活性较高。但是,带来一定程度的性能降低,同时复杂性有所提高。使用时候,你必须确认所使用的参数的功能注意事项。一般情况下,使用unique_lock的频率较lock_guard低些,如果你使用的话请注意下构造函数的相关参数类型模式即可。

参考

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值