TheRiver | blog

You have reached the world's edge, none but devils play past here

0%

C++ std::mutex

std::mutex/std::lock_guard/std::unique_lock/std::scoped_lock/std::recursive_mutex等族函数简单梳理

date author gcc kernel glibc
20220331 river gcc-11.2.0 linux-4.18.1 glibc-2.35
class since file meaning
std::mutex C++11 std_mutex.h pthread_mutex_t的封装
std::lock_guard C++11 std_mutex.h 简单的mutex的守卫类,没有对外接口,支持std::adopt_lock
std::unique_lock C++11 unique_lock.h mutex的守卫类,有对外接口,支持std::adopt_lock/std::defer_lock/std::try_to_lock
std::scoped_lock C++17 mutex 支持对多个mutex的加锁,内部依赖std::lock(…)
std::recursive_mutex C++11 mutex 递归锁,通过pthread_mutex_t的attr中参数实现递归
std::adopt_lock C++17 std_mutex.h 不执行lock(),但保存mutex的地址/引用
std::defer_lock C++11 std_mutex.h 不执行lock(),也不own
std::try_to_lock C++11 std_mutex.h 通过pthread_mutex_trylock的结果决定是否own
std::lock C++11 mutex 依赖c++17的模板推导机制,支持锁多个lockable object

std::mutex

#include <gcc-11.2.0/libstdc++-v3/include/bits/std_mutex.h>

datastruct

mutex::__mutex_base::_M_mutex即是pthread_mutex_t.

lock()

1
2
3
4
5
6
7
8
9
void
lock()
{
int __e = __gthread_mutex_lock(&_M_mutex);

// EINVAL, EAGAIN, EBUSY, EINVAL, EDEADLK(may)
if (__e)
__throw_system_error(__e);
}

对应pthread_mutex_lock,失败抛异常.

try_lock()

1
2
3
4
5
6
bool
try_lock() noexcept
{
// XXX EINVAL, EAGAIN, EBUSY
return !__gthread_mutex_trylock(&_M_mutex);
}

对应pthread_mutex_trylock,不抛异常.

unlock()

1
2
3
4
5
6
void
unlock()
{
// XXX EINVAL, EAGAIN, EPERM
__gthread_mutex_unlock(&_M_mutex);
}

对应pthread_mutex_unlock.

native_handle()

1
2
3
native_handle_type
native_handle() noexcept
{ return &_M_mutex; }

返回pthread_mutex_t.

std::lock_guard

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
/** @brief A simple scoped lock type.
*
* A lock_guard controls mutex ownership within a scope, releasing
* ownership in the destructor.
*/
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) 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;
};

非常简单的一个守卫类。传的是一个”mutex”的引用, 没有提供对外的函数。如果带std::adopt_lock初始化,则只引用不加锁,如下:

1
std::lock_guard<std::mutex> lock(m,std::adopt_lock);

构造函数必须传入一个”mutex”的左值。

1
2
3
4
mutex m;
lock_guard<mutex> l1; // err
lock_guard<mutex> l2(); // 成函数声明了
lock_guard<mutex> l3(m); // ok

unique_lock

相比lock_guard,多了一堆外部接口。多了一个own成员表示是否持有,自由度更高,支持defer_lock

def

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
/** @brief A movable scoped lock type.
*
* A unique_lock controls mutex ownership within a scope. Ownership of the
* mutex can be delayed until after construction and can be transferred
* to another unique_lock by move construction or move assignment. If a
* mutex lock is owned when the destructor runs ownership will be released.
*
* @ingroup mutexes
*/
template<typename _Mutex>
class unique_lock
{
public:
typedef _Mutex mutex_type;

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

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)
{ }

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

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

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

private:
mutex_type* _M_device;
bool _M_owns;
};

lock()

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}
}

unique_lock构造函数可以不传”mutex”进来,所以分3种情况,最终调用posix的lock().

  • 没关联Mutex
  • 没hold mutex
  • hold mutex

try_lock()

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}
}

也是3种情况,和lock()差不多,调用posix的try_lock().

try_lock_for()

attempts to lock (i.e., takes ownership of) the associated TimedLockable mutex, returns if the mutex has been unavailable for the specified time duration (public member function)

支持不同时间格式的timeout来try_lock,底层是posix的pthread_mutex_timedlock/pthread_mutex_clocklock

try_lock_until

tries to lock (i.e., takes ownership of) the associated TimedLockable mutex, returns if the mutex has been unavailable until specified time point has been reached (public member function)

支持不同时间格式的timeout来try_lock,底层是posix的pthread_mutex_timedlock/pthread_mutex_clocklock

unlock()

1
2
3
4
5
6
7
8
9
10
11
void
unlock()
{
if (!_M_owns)
__throw_system_error(int(errc::operation_not_permitted));
else if (_M_device)
{
_M_device->unlock();
_M_owns = false;
}
}

未拥有就unlock会异常

swap()

swaps state with another std::unique_lock(public member function)

release()

disassociates the associated mutex without unlocking (i.e., releasing ownership of) it
(public member function)

1
2
3
4
5
6
7
8
mutex_type*
release() noexcept
{
mutex_type* __ret = _M_device;
_M_device = 0;
_M_owns = false;
return __ret;
}

释放所有权,状态置为空,返回原来的mutex的地址。

mutex()

returns a pointer to the associated mutex (public member function)

1
2
3
mutex_type*
mutex() const noexcept
{ return _M_device; }

返回锁的地址

owns_lock

tests whether the lock owns (i.e., has locked) its associated mutex (public member function)

1
2
3
bool
owns_lock() const noexcept
{ return _M_owns; }

scoped_lock

代码不多,放上来了。scoped_lock只接收一个参数的话和lock_guard看起来没啥区别,主要是对于多个参数的使用比较好,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// s1
{
std::mutex m1, m2;
std::lock(m1, m2);
std::lock_guard<std::mutex> l1(m1, std::adopt_lock);
std::lock_guard<std::mutex> l2(m2, std::adopt_lock);
}

// s2
{
std::mutex m1, m2;
std::scoped_lock<std::mutex, std::mutex> sl(m1, m2);
}
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
#if __cplusplus >= 201703L
#define __cpp_lib_scoped_lock 201703
/** @brief A scoped lock type for multiple lockable objects.
*
* A scoped_lock controls mutex ownership within a scope, releasing
* ownership in the destructor.
*/
template<typename... _MutexTypes>
class scoped_lock
{
public:
explicit scoped_lock(_MutexTypes&... __m) : _M_devices(std::tie(__m...))
{ std::lock(__m...); }

explicit scoped_lock(adopt_lock_t, _MutexTypes&... __m) noexcept
: _M_devices(std::tie(__m...))
{ } // calling thread owns mutex

~scoped_lock()
{ std::apply([](auto&... __m) { (__m.unlock(), ...); }, _M_devices); }

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

private:
tuple<_MutexTypes&...> _M_devices;
};

template<>
class scoped_lock<>
{
public:
explicit scoped_lock() = default;
explicit scoped_lock(adopt_lock_t) noexcept { }
~scoped_lock() = default;

scoped_lock(const scoped_lock&) = delete;
scoped_lock& operator=(const scoped_lock&) = delete;
};

template<typename _Mutex>
class scoped_lock<_Mutex>
{
public:
using mutex_type = _Mutex;

explicit scoped_lock(mutex_type& __m) : _M_device(__m)
{ _M_device.lock(); }

explicit scoped_lock(adopt_lock_t, mutex_type& __m) noexcept
: _M_device(__m)
{ } // calling thread owns mutex

~scoped_lock()
{ _M_device.unlock(); }

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

private:
mutex_type& _M_device;
};
#endif // C++17

recursive_mutex

线程对已经获取的 std::mutex (已经上锁)再次上锁是错误的,尝试这样做会导致未定义行为。在某些情况下,一个线程会尝试在释放一个互斥量前多次获取。因此,C++标准库提供了 std::recursive_mutex 类。

recursive_mutex::__recursive_mutex_base::_M_mutex就是pthread_mutex_t.
recursive_mutex和mutex的区别主要在构造函数中,给pthread_mutex_t加了attr(PTHREAD_MUTEX_RECURSIVE):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static inline int
__gthread_recursive_mutex_init_function (__gthread_recursive_mutex_t *__mutex)
{
if (__gthread_active_p ())
{
pthread_mutexattr_t __attr;
int __r;

__r = __gthrw_(pthread_mutexattr_init) (&__attr);
if (!__r)
__r = __gthrw_(pthread_mutexattr_settype) (&__attr,
PTHREAD_MUTEX_RECURSIVE);
if (!__r)
__r = __gthrw_(pthread_mutex_init) (__mutex, &__attr);
if (!__r)
__r = __gthrw_(pthread_mutexattr_destroy) (&__attr);
return __r;
}
return 0;
}

PTHREAD_MUTEX_RECURSIVE在posix下的实现,就是保存了tid+count,来维持一个引用计数,如下:

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
else if (__builtin_expect (PTHREAD_MUTEX_TYPE (mutex)
== PTHREAD_MUTEX_RECURSIVE_NP, 1))
{
/* Recursive mutex. */
pid_t id = THREAD_GETMEM (THREAD_SELF, tid);

/* Check whether we already hold the mutex. */
if (mutex->__data.__owner == id)
{
/* Just bump the counter. */
if (__glibc_unlikely (mutex->__data.__count + 1 == 0))
/* Overflow of the counter. */
return EAGAIN;

++mutex->__data.__count;

return 0;
}

/* We have to get the mutex. */
LLL_MUTEX_LOCK_OPTIMIZED (mutex);

assert (mutex->__data.__owner == 0);
mutex->__data.__count = 1;
}

unlock

1
2
3
4
5
6
7
8
9
10
11
12
else if (__builtin_expect (PTHREAD_MUTEX_TYPE (mutex)
== PTHREAD_MUTEX_RECURSIVE_NP, 1))
{
/* Recursive mutex. */
if (mutex->__data.__owner != THREAD_GETMEM (THREAD_SELF, tid))
return EPERM;

if (--mutex->__data.__count != 0)
/* We still hold the mutex. */
return 0;
goto normal;
}

std::lock

std::lock对可变参数的对象进行加锁,实现上不放代码了,简单地说:

  • 通过unique_lock来加锁
  • 通过unique_lock::release释放所有权,不解锁
  • 通过模板递归实例化来处理…args

reference

https://en.cppreference.com/w/cpp/thread/mutex

----------- ending -----------