C++ 标准为我们提供了以下基本的锁类型
- lock_guard(C++11)
- unique_lock(C++11)
- shared_lock(C++14)
- scoped_lock(C++17)
以及还提供了几个与锁类型相关的 Tag 类:
defer_lock_t
不获得互斥的所有权try_to_lock_t
尝试获得互斥的所有权而不阻塞adopt_lock_t
假设调用方线程已拥有互斥的所有权
struct defer_lock_t { };
/// Try to acquire ownership of the mutex without blocking.
struct try_to_lock_t { };/// Assume the calling thread has already obtained mutex ownership
/// and manage it.
struct adopt_lock_t { };constexpr defer_lock_t defer_lock { };
constexpr try_to_lock_t try_to_lock { };
constexpr adopt_lock_t adopt_lock { };
std::lock_guard
类 lock_guard
是互斥体包装器,为在作用域块期间占有互斥提供便利 RAII 风格机制;创建 lock_guard
对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard
对象的作用域时,销毁 lock_guard
并释放互斥;lock_guard
类不可复制。
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(mutex_type& __m, adopt_lock_t) : _M_device(__m)
{ } // calling thread owns mutex
~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 对象构造时,传入的 Mutex 对象(即它所管理的 Mutex 对象)会被当前线程锁住。在lock_guard 对象被析构时,它所管理的 Mutex 对象会自动解锁,由于不需要程序员手动调用 lock 和 unlock 对 Mutex 进行上锁和解锁操作,因此这也是最简单安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的 Mutex 对象可以正确进行解锁操作,极大地简化了程序员编写与 Mutex 相关的异常处理代码。
lock_guard 对象并不负责管理 Mutex 对象的生命周期,lock_guard 对象只是简化了 Mutex 对象的上锁和解锁操作,方便线程对互斥量上锁,即在某个 lock_guard 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard 的生命周期结束之后,它所管理的锁对象会被解锁。
简单的测试:
#include <iostream>
#include <mutex>
using namespace std;
#define M 10000000
class A
{
public:
A() :_count(0) {}
void print()
{
for (size_t i = 0; i < M; i++)
{
_mutex.lock();//加锁
//adopt_lock标记当前已获得锁,lock_guard构造函数中并不会再次执行lock
lock_guard<mutex> lock(_mutex,adopt_lock);
_count++;
}
}
void print2()
{
for (size_t i = 0; i < M; i++)
{
lock_guard<mutex> lock(_mutex);
_count++;
}
}
private:
mutex _mutex;
public:
int _count;
};
int main()
{
A a1;
thread t1(&A::print,&a1);
thread t2(&A::print2, &a1);
t1.join();
t2.join();
cout << " print count: " << a1._count << endl;
return system("pause");
}
std::unique_lock
类 unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。
类 unique_lock 可移动,但不可复制——它满足可移动构造 (MoveConstructible) 和可移动赋值 (MoveAssignable) 但不满足可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 。
类 unique_lock 满足基本可锁定 (BasicLockable) 要求。若 Mutex 满足可锁定 (Lockable) 要求,则 unique_lock 亦满足可锁定 (Lockable) 要求(例如:能用于 std::lock ) ;若 Mutex 满足可定时锁定 (TimedLockable) 要求,则 unique_lock 亦满足可定时锁定 (TimedLockable) 要求。
1. 构造函数与析构函数
以下是unique_lock的构造函数与析构函数:
unique_lock() noexcept
: _M_device(0), _M_owns(false)
{ }
explicit unique_lock(mutex_type& __m)
: _M_device(&__m), _M_owns(false)
{
lock();
_M_owns = true;
}
unique_lock(mutex_type& __m, defer_lock_t) noexcept
: _M_device(&__m), _M_owns(false)
{ }
unique_lock(mutex_type& __m, try_to_lock_t)
: _M_device(&__m), _M_owns(_M_device->try_lock())
{ }
unique_lock(mutex_type& __m, adopt_lock_t)
: _M_device(&__m), _M_owns(true)
{
// XXX calling thread owns mutex
}
template<typename _Clock, typename _Duration>
unique_lock(mutex_type& __m,
const chrono::time_point<_Clock, _Duration>& __atime)
: _M_device(&__m), _M_owns(_M_device->try_lock_until(__atime))
{ }
template<typename _Rep, typename _Period>
unique_lock(mutex_type& __m,
const chrono::duration<_Rep, _Period>& __rtime)
: _M_device(&__m), _M_owns(_M_device->try_lock_for(__rtime))
{ }
~unique_lock()
{
if (_M_owns)
unlock();
}
default (1) | unique_lock() noexcept; | |
---|---|---|
locking (2) | explicit unique_lock(mutex_type& m); | 新创建的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.lock() 对 Mutex 对象进行上锁,如果此时另外某个 unique_lock 对象已经管理了该 Mutex 对象 m,则当前线程将会被阻塞。 |
try-locking (3) | unique_lock(mutex_type& m, try_to_lock_t tag); | 新创建的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.try_lock() 对 Mutex 对象进行上锁,但如果上锁不成功,并不会阻塞当前线程。 |
deferred (4) | unique_lock(mutex_type& m, defer_lock_t tag) noexcept; | 新创建的 unique_lock 对象管理 Mutex 对象 m,但是在初始化的时候并不锁住 Mutex 对象。 |
adopting (5) | unique_lock(mutex_type& m, adopt_lock_t tag); | 新创建的 unique_lock 对象管理 Mutex 对象 m, m 应该是一个已经被当前线程锁住的 Mutex 对象。(并且当前新创建的 unique_lock 对象拥有对锁(Lock)的所有权)。 |
locking for (6) | template <class Rep, class Period> unique_lock(mutex_type& m, const chrono::duration<Rep,Period>& rel_time); | 新创建的 unique_lock 对象管理 Mutex 对象 m,并试图通过调用 m.try_lock_for(rel_time) 来锁住 Mutex 对象一段时间(rel_time)。 |
locking until (7) | template <class Clock, class Duration> unique_lock(mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time); | 新创建的 unique_lock 对象管理 Mutex 对象m,并试图通过调用 m.try_lock_until(abs_time) 来在某个时间点(abs_time)之前锁住 Mutex 对象。 |
2. copy assignment 和 copy constructor
unique_lock 对象不能被拷贝和赋值。
unique_lock(const unique_lock&) = delete;
unique_lock& operator=(const unique_lock&) = delete;
3. move assignment 和 move constructor
unique_lock(unique_lock&& __u) noexcept
: _M_device(__u._M_device), _M_owns(__u._M_owns)
{
__u._M_device = 0;
__u._M_owns = false;
}
unique_lock& operator=(unique_lock&& __u) noexcept
{
if(_M_owns)
unlock();
unique_lock(std::move(__u)).swap(*this);
__u._M_device = 0;
__u._M_owns = false;
return *this;
}
新创建的 unique_lock 对象获得了由 x 所管理的 Mutex 对象的所有权(包括当前 Mutex 的状态)。调用 move 构造之后, x 对象如同通过默认构造函数所创建的,就不再管理任何 Mutex 对象了。
4. 成员函数
锁定关联互斥 (公开成员函数) | |
尝试锁定关联互斥,若互斥不可用则返回 (公开成员函数) | |
试图锁定关联的可定时锁定 (TimedLockable) 互斥,若互斥在给定时长中不可用则返回 (公开成员函数) | |
尝试锁定关联可定时锁定 (TimedLockable) 互斥,若抵达指定时间点互斥仍不可用则返回 (公开成员函数) | |
解锁关联互斥 (公开成员函数) | |
修改器 | |
与另一 std::unique_lock 交换状态 (公开成员函数) | |
将关联互斥解关联而不解锁它 (公开成员函数) | |
观察器 | |
返回指向关联互斥的指针 (公开成员函数) | |
测试锁是否占有其关联互斥 (公开成员函数) | |
测试锁是否占有其关联互斥 (公开成员函数) |
简单的测试
#include <iostream>
#include <mutex>
using namespace std;
#define M 1000000
class A
{
public:
A() :count(0) {}
void myPrint()
{
for (int i = 0; i < M; i++)
{
unique_lock<mutex> lock(_m_Mutex,try_to_lock);
if (lock.owns_lock()) {
count++;
cout << " 已获得锁" << endl;
}
else
cout << "未获得锁" << endl;
}
}
int getCount()
{
return count;
}
private:
mutex _m_Mutex;
int count;
};
int main()
{
A a1;
thread t1(&A::myPrint,&a1);
thread t2(&A::myPrint,&a1);
t1.join();
t2.join();
cout << "A::myPrint count: "<< a1.getCount() << endl;
std::mutex foo, bar;
std::unique_lock<std::mutex> lck1, lck2;
// 仍未实际取锁
lck1 = std::unique_lock<std::mutex>(bar, std::defer_lock);
lck2 = std::unique_lock<std::mutex>(foo, std::defer_lock);
std::lock(lck1, lck2); // 锁两个 unique_lock 而不死锁
return system("pause");
}
以上参考: