一、std::lock_guard类

1.lock_guard是一个模板类, 用于管理互斥量的加锁和解锁

在需要加锁的代码块中创建一个lock_guard对象(调用构造函数), 在lock_guard对象的生命周期结束时(调用析构函数), 会自动解锁

lock_guard禁用拷贝构造函数和赋值运算符,无法复制和移动

常用构造函数:

1
explicit lock_guard( mutex_type& m );

以一个互斥量作为参数。

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <thread>
#include <mutex>

using std::cin;
using std::cout;

std::mutex mtx;
int shared_data = 0;
void func()
{
for (int i = 0; i < 1000000; i++)
{
std::lock_guard<std::mutex> lock(mtx); // 在作用域内自动加锁解锁
shared_data++;
}
}

int main()
{
std::ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);

std::thread t1(func), t2(func);
t1.join(), t2.join();
cout << shared_data << '\n';
return 0;
}

程序正常输出2000000,并且多次运行,不会出现冲突情况。

附:

类 lock_guard 是互斥体包装器,为在作用域块期间占有互斥体提供便利的 RAII 风格机制。

当创建 lock_guard 对象时,它尝试接收给定互斥体的所有权。当控制离开创建 lock_guard 对象的作用域时,销毁 lock_guard 并释放互斥体。

lock_guard 类不可复制。

一种常见的新手错误是“忘记”给 lock_guard 变量命名,例如 std::lock_guard(mtx);(它默认构造了一个名为 mtx 的 lock_guard 变量,或者 std::lock_guard{mtx};(它构造

了一个纯右值对象并立即销毁),而并未真正为作用域的剩余部分构造持有互斥体的锁。

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
template <typename _Mutex>
class lock_guard
{
public:
typedef _Mutex mutex_type;

[[__nodiscard__]]
explicit lock_guard(mutex_type &__m) : _M_device(__m)
{
_M_device.lock(); // 构造及加锁
}

[[__nodiscard__]]
lock_guard(mutex_type &__m, adopt_lock_t) noexcept : _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;
};

二、std::unique_lock类

相较于lock_guard,标准库为我们提供了一个更强大的工具:unique_lock类:

1.std::unique_lock是一个模板类, 用于管理互斥量的加锁和解锁。

2.std::unique_lock提供了更多的灵活性, 可以在任何时候手动加锁和解锁。

3.std::unique_lock可以被复制和移动。

4.std::unique_lock可以在构造函数中指定是否加锁。

5.部分成员函数lock(),try_lock(),try_lock_for(),try_lock_until(),unlock()。

lock_guard()是std::unique_lock的一个特例,缺点也显而易见:会占用更多的资源

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
template <typename _Mutex>
class unique_lock
{
public:
typedef _Mutex mutex_type;

unique_lock() noexcept
: _M_device(0), _M_owns(false)
{
}

[[__nodiscard__]]
explicit unique_lock(mutex_type &__m)
: _M_device(std::__addressof(__m)), _M_owns(false)
{
lock();
_M_owns = true;
}

unique_lock(mutex_type &__m, defer_lock_t) noexcept
: _M_device(std::__addressof(__m)), _M_owns(false)
{
}

[[__nodiscard__]]
unique_lock(mutex_type &__m, try_to_lock_t)
: _M_device(std::__addressof(__m)), _M_owns(_M_device->try_lock())
{
}

[[__nodiscard__]]
unique_lock(mutex_type &__m, adopt_lock_t) noexcept
: _M_device(std::__addressof(__m)), _M_owns(true)
{
// XXX calling thread owns mutex
}

template <typename _Clock, typename _Duration>
[[__nodiscard__]]
unique_lock(mutex_type &__m,
const chrono::time_point<_Clock, _Duration> &__atime)
: _M_device(std::__addressof(__m)),
_M_owns(_M_device->try_lock_until(__atime))
{
}

template <typename _Rep, typename _Period>
[[__nodiscard__]]
unique_lock(mutex_type &__m,
const chrono::duration<_Rep, _Period> &__rtime)
: _M_device(std::__addressof(__m)),
_M_owns(_M_device->try_lock_for(__rtime))
{
}

~unique_lock()
{
if (_M_owns)
unlock();
}

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

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;
}

void
lock()
{
if (!_M_device)
__throw_system_error(int(errc::operation_not_permitted));
else if (_M_owns)
__throw_system_error(int(errc::resource_deadlock_would_occur));
else
{
_M_device->lock();
_M_owns = true;
}
}

_GLIBCXX_NODISCARD
bool
try_lock()
{
if (!_M_device)
__throw_system_error(int(errc::operation_not_permitted));
else if (_M_owns)
__throw_system_error(int(errc::resource_deadlock_would_occur));
else
{
_M_owns = _M_device->try_lock();
return _M_owns;
}
}

template <typename _Clock, typename _Duration>
_GLIBCXX_NODISCARD bool
try_lock_until(const chrono::time_point<_Clock, _Duration> &__atime)
{
if (!_M_device)
__throw_system_error(int(errc::operation_not_permitted));
else if (_M_owns)
__throw_system_error(int(errc::resource_deadlock_would_occur));
else
{
_M_owns = _M_device->try_lock_until(__atime);
return _M_owns;
}
}

template <typename _Rep, typename _Period>
_GLIBCXX_NODISCARD bool
try_lock_for(const chrono::duration<_Rep, _Period> &__rtime)
{
if (!_M_device)
__throw_system_error(int(errc::operation_not_permitted));
else if (_M_owns)
__throw_system_error(int(errc::resource_deadlock_would_occur));
else
{
_M_owns = _M_device->try_lock_for(__rtime);
return _M_owns;
}
}

void
unlock()
{
if (!_M_owns)
__throw_system_error(int(errc::operation_not_permitted));
else if (_M_device)
{
_M_device->unlock();
_M_owns = false;
}
}

void
swap(unique_lock &__u) noexcept
{
std::swap(_M_device, __u._M_device);
std::swap(_M_owns, __u._M_owns);
}

mutex_type *
release() noexcept
{
mutex_type *__ret = _M_device;
_M_device = 0;
_M_owns = false;
return __ret;
}

_GLIBCXX_NODISCARD
bool
owns_lock() const noexcept
{
return _M_owns;
}

explicit operator bool() const noexcept
{
return owns_lock();
}

_GLIBCXX_NODISCARD
mutex_type *
mutex() const noexcept
{
return _M_device;
}

private:
mutex_type *_M_device;
bool _M_owns;
};

我们选几个典型来熟悉它的用法:

std::defer_lock

传入std::defer_lock,可以选择性延迟加锁:

1
2
unique_lock(mutex_type &__m, defer_lock_t) noexcept 
: _M_device(std::__addressof(__m)), _M_owns(false) {} // 可见构造函数并没有加锁

try_lock_for()

尝试加锁,如果遇到mtx上锁的情况,可以选择性等待指定的时间,如果当前mtx可以加锁,就会给它加锁;若时间到后仍没有解锁,就会返回 false,并且跳过。

:若使用该函数,mtx需使用std::timed_mutex时间锁。

1
2
3
std::timed_mutex t_mtx;
std::unique_lock<std::timed_mutex> u_lock(t_mtx, std::defer_lock);
u_lock.try_lock_for(std::chrono::milliseconds(1000));

std::swap

特化的std::swap可对两个unique_lock进行交换。

复制构造,赋值禁用,但移动构造可用

1
2
3
4
5
6
7
8
unique_lock &operator=(unique_lock &&__u) noexcept
{
if (_M_owns)
unlock();
__u._M_device = 0;
__u._M_owns = false;
unique_lock(std::move(__u)).swap(*this);
}