~~~~
之前使用锁的时候,要自动释放,就自己写了一个类,通过构造和析构函数自动释放锁,后来发现了std::lock_guard,然后就没有使用自己写的自动释放类了。
~~~~
今天又发现了std::unique_lock,功能比std::lock_guard要多,同时也有自动加锁和解锁,功能更加灵活,但是相应地性能没有std::lock_guard要好。
先附上一个自己写的自动释放的类
class CAutoMutex{
public:
CAutoMutex(mutex* val){
m_mutex = val;
if(m_mutex)
m_mutex->lock();
}
~CAutoMutex(){
if(m_mutex)
m_mutex->unlock();
m_mutex = NULL;
}
private:
mutex *m_mutex;
};
我们可以看一下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;
};
然后我们写一个std::lock_guard的例子,可以用我上面写的类直接替换哦。
#include <thread>
#include <mutex>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int a = 1000;
mutex g_mutex;
//输出
void test(int val1, int val2){
printf("thread %d run. a = %d.\n", val1, val2);
}
void thread_run(int val){
while(true)
{
{
//访问a
if(a < 0) break;
lock_guard<mutex> lock(g_mutex);
test(val, a);
a--;
}
this_thread::sleep_for(chrono::milliseconds(10));
}
}
int main(){
printf("start test.\n");
std::vector<std::thread> ver; //保存线程
for(int i = 0; i < 5; i++){
std::thread t = thread(thread_run, i);
ver.emplace_back(std::move(t)); //保存
}
//等待结束
std::for_each(ver.begin(), ver.end(), std::mem_fn(&std::thread::join));
printf("finish test.\n");
}
下面是编译,外加结果:
qilimi@qilimi-desktop:~/test$ vim test_lick_guard.cpp
qilimi@qilimi-desktop:~/test$ g++ -o test test_lick_guard.cpp -lpthread -std=c++11
qilimi@qilimi-desktop:~/test$ ./test
start test.
thread 0 run. a = 100.
thread 1 run. a = 99.
thread 2 run. a = 98.
thread 3 run. a = 97.
thread 4 run. a = 96.
thread 3 run. a = 95.
thread 2 run. a = 94.
thread 1 run. a = 93.
thread 0 run. a = 92.
thread 4 run. a = 91.
thread 3 run. a = 90.
...
thread 1 run. a = 10.
thread 3 run. a = 9.
thread 0 run. a = 8.
thread 2 run. a = 7.
thread 4 run. a = 6.
thread 3 run. a = 5.
thread 0 run. a = 4.
thread 1 run. a = 3.
thread 2 run. a = 2.
thread 4 run. a = 1.
thread 0 run. a = 0.
thread 2 run. a = -1.
thread 3 run. a = -2.
thread 4 run. a = -3.
thread 1 run. a = -4.
finish test.
qilimi@qilimi-desktop:~/test$
然后我们再来看看std::unique_lock,有哪些功能,我们先上std::unique_lock的源码:
// CLASS TEMPLATE unique_lock
template<class _Mutex>
class unique_lock
{ // whizzy class with destructor that unlocks mutex
public:
typedef _Mutex mutex_type;
// CONSTRUCT, ASSIGN, AND DESTROY
unique_lock() noexcept
: _Pmtx(nullptr), _Owns(false)
{ // default construct
}
explicit unique_lock(_Mutex& _Mtx)
: _Pmtx(_STD addressof(_Mtx)), _Owns(false)
{ // construct and lock
_Pmtx->lock();
_Owns = true;
}
unique_lock(_Mutex& _Mtx, adopt_lock_t)
: _Pmtx(_STD addressof(_Mtx)), _Owns(true)
{ // construct and assume already locked
}
unique_lock(_Mutex& _Mtx, defer_lock_t) noexcept
: _Pmtx(_STD addressof(_Mtx)), _Owns(false)
{ // construct but don't lock
}
unique_lock(_Mutex& _Mtx, try_to_lock_t)
: _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock())
{ // construct and try to lock
}
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
}
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&& _Other) noexcept
: _Pmtx(_Other._Pmtx), _Owns(_Other._Owns)
{ // destructive copy
_Other._Pmtx = nullptr;
_Other._Owns = false;
}
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;
void lock()
{ // lock the mutex
_Validate();
_Pmtx->lock();
_Owns = true;
}
_NODISCARD bool try_lock()
{ // try to lock the mutex
_Validate();
_Owns = _Pmtx->try_lock();
return (_Owns);
}
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);
}
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);
}
void unlock()
{ // try to unlock the mutex
if (!_Pmtx || !_Owns)
_THROW(system_error(
_STD make_error_code(errc::operation_not_permitted)));
_Pmtx->unlock();
_Owns = false;
}
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(
_STD make_error_code(errc::operation_not_permitted)));
if (_Owns)
_THROW(system_error(
_STD make_error_code(errc::resource_deadlock_would_occur)));
}
};
~~~~
我们可以发现,如果带一个参数,效果和std::lock_guard一样的,它的带两个参数的构造函数有三个,分别为构造且默认已经锁住(带adopt_lock_t,已经加锁,这个在std::lock_guard里面也有)、构造且不加锁(带defer_lock_t,没有加锁)、构造且尝试加锁(带try_to_lock_t,尝试加锁,需要查询是否加锁成功)、延时加锁(带时间参数)’,同时多了一个变量,保存有没有加锁,可以去获取它的状态。
~~~~
代码很简单,大家可以自己看一看,我们就在实例一次:
#include <thread>
#include <mutex>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int a = 1000;
mutex g_mutex;
//输出
void test(int val1, int val2){
printf("thread %d run. a = %d.\n", val1, val2);
}
void thread_run(int val){
while(true)
{
{
//访问a
if(a < 0) break;
//尝试加锁
unique_lock<mutex> lock(g_mutex, try_to_lock);
//判断是否加锁
if(lock.owns_lock()){
test(val, a);
a--;
}
}
this_thread::sleep_for(chrono::milliseconds(10));
}
}
int main(){
printf("start test.\n");
std::vector<std::thread> ver; //保存线程
for(int i = 0; i < 5; i++){
std::thread t = thread(thread_run, i);
ver.emplace_back(std::move(t)); //保存
}
//等待结束
std::for_each(ver.begin(), ver.end(), std::mem_fn(&std::thread::join));
printf("finish test.\n");
}
下面是编译和输出
qilimi@qilimi-desktop:~/test$ vim test_lick_guard3.cpp
qilimi@qilimi-desktop:~/test$ g++ -o test3 test_lick_guard3.cpp -lpthread -std=c++11
qilimi@qilimi-desktop:~/test$ ./test3
start test.
thread 0 run. a = 100.
thread 0 run. a = 99.
thread 4 run. a = 98.
thread 2 run. a = 97.
thread 3 run. a = 96.
thread 1 run. a = 95.
thread 4 run. a = 94.
thread 1 run. a = 93.
thread 0 run. a = 92.
thread 1 run. a = 91.
thread 0 run. a = 90.
...
thread 2 run. a = 10.
thread 2 run. a = 9.
thread 1 run. a = 8.
thread 2 run. a = 7.
thread 0 run. a = 6.
thread 3 run. a = 5.
thread 2 run. a = 4.
thread 0 run. a = 3.
thread 4 run. a = 2.
thread 2 run. a = 1.
thread 1 run. a = 0.
finish test.
大家仔细看源码就会发现unique_lock还可以临时解锁,然后又加锁,相对功能比lock_guard还是多很多的。