ReentrantLock.h
8.7 KB
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
#pragma once
#include "../C/Baselib_ReentrantLock.h"
#include "Time.h"
namespace baselib
{
BASELIB_CPP_INTERFACE
{
// In computer science, the reentrant mutex (recursive mutex, recursive lock) is particular type of mutual exclusion (mutex) device that may be locked multiple
// times by the same process/thread, without causing a deadlock.
// While any attempt to perform the "lock" operation on an ordinary mutex (lock) would either fail or block when the mutex is already locked, on a recursive
// mutex this operation will succeed if and only if the locking thread is the one that already holds the lock. Typically, a recursive mutex tracks the number
// of times it has been locked, and requires equally many unlock operations to be performed before other threads may lock it.
//
// "Reentrant mutex", Wikipedia: The Free Encyclopedia
// https://en.wikipedia.org/w/index.php?title=Reentrant_mutex&oldid=818566928
//
// For optimal performance, baselib::ReentrantLock should be stored at a cache aligned memory location.
class ReentrantLock
{
public:
// non-copyable
ReentrantLock(const ReentrantLock& other) = delete;
ReentrantLock& operator=(const ReentrantLock& other) = delete;
// non-movable (strictly speaking not needed but listed to signal intent)
ReentrantLock(ReentrantLock&& other) = delete;
ReentrantLock& operator=(ReentrantLock&& other) = delete;
// Creates a reentrant lock synchronization primitive.
// If there are not enough system resources to create a lock, process abort is triggered.
ReentrantLock() : m_ReentrantLockData(Baselib_ReentrantLock_Create())
{
}
// Reclaim resources and memory held by lock.
//
// If threads are waiting on the lock, calling free may trigger an assert and may cause process abort.
// Calling this function with a nullptr result in a no-op
~ReentrantLock()
{
Baselib_ReentrantLock_Free(&m_ReentrantLockData);
}
// Acquire lock.
//
// If lock is already acquired by the current thread this function increase the lock count so that an equal number of calls to Baselib_ReentrantLock_Release needs
// to be made before the lock is released.
// If lock is held by another thread, this function wait for lock to be released.
//
// This function is guaranteed to emit an acquire barrier.
inline void Acquire()
{
return Baselib_ReentrantLock_Acquire(&m_ReentrantLockData);
}
// Try to acquire lock and return immediately.
// If lock is already acquired by the current thread this function increase the lock count so that an equal number of calls to Baselib_ReentrantLock_Release needs
// to be made before the lock is released.
//
// When lock is acquired this function is guaranteed to emit an acquire barrier.
//
// Return: true if lock was acquired.
COMPILER_WARN_UNUSED_RESULT
FORCE_INLINE bool TryAcquire()
{
return Baselib_ReentrantLock_TryAcquire(&m_ReentrantLockData);
}
// Try to acquire lock.
// If lock is already acquired by the current thread this function increase the lock count so that an equal number of calls to Baselib_ReentrantLock_Release needs
// to be made before the lock is released.
// If lock is held by another thread, this function wait for timeoutInMilliseconds for lock to be released.
//
// When lock is acquired this function is guaranteed to emit an acquire barrier.
//
// TryAcquire with a zero timeout differs from TryAcquire() in that TryAcquire() is guaranteed to be a user space operation
// while TryAcquire with zero timeout may enter the kernel and cause a context switch.
//
// Timeout passed to this function may be subject to system clock resolution.
// If the system clock has a resolution of e.g. 16ms that means this function may exit with a timeout error 16ms earlier than originally scheduled.
//
// Return: true if lock was acquired.
COMPILER_WARN_UNUSED_RESULT
FORCE_INLINE bool TryTimedAcquire(const timeout_ms timeoutInMilliseconds)
{
return Baselib_ReentrantLock_TryTimedAcquire(&m_ReentrantLockData, timeoutInMilliseconds.count());
}
// Release lock.
// If lock count is still higher than zero after the release operation then lock remain in a locked state.
// If lock count reach zero the lock is unlocked and made available to other threads
//
// When the lock is released this function is guaranteed to emit a release barrier.
//
// Calling this function from a thread that doesn't own the lock triggers an assert in debug and causes undefined behavior in release builds.
FORCE_INLINE void Release()
{
return Baselib_ReentrantLock_Release(&m_ReentrantLockData);
}
// Acquire lock and invoke user defined function.
// If lock is held by another thread, this function wait for lock to be released.
//
// When a lock is acquired this function is guaranteed to emit an acquire barrier.
//
// Example usage:
// lock.AcquireScoped([] {
// enteredCriticalSection++;
// });
template<class FunctionType>
FORCE_INLINE void AcquireScoped(const FunctionType& func)
{
ReleaseOnDestroy releaseScope(*this);
Acquire();
func();
}
// Try to acquire lock and invoke user defined function.
// If lock is held by another thread, this function wait for timeoutInMilliseconds for lock to be released.
// On failure to obtain lock the user defined function is not invoked.
//
// When lock is acquired this function is guaranteed to emit an acquire barrier.
//
// Example usage:
// lock.TryAcquireScoped([] {
// enteredCriticalSection++;
// });
//
// Return: true if lock was acquired.
template<class FunctionType>
FORCE_INLINE bool TryAcquireScoped(const FunctionType& func)
{
if (TryAcquire())
{
ReleaseOnDestroy releaseScope(*this);
func();
return true;
}
return false;
}
// Try to acquire lock and invoke user defined function.
// If lock is held by another thread, this function wait for timeoutInMilliseconds for lock to be released.
// On failure to obtain lock the user defined function is not invoked.
//
// When lock is acquired this function is guaranteed to emit an acquire barrier.
//
// Timeout passed to this function may be subject to system clock resolution.
// If the system clock has a resolution of e.g. 16ms that means this function may exit with a timeout error 16ms earlier than originally scheduled.
//
// Example usage:
// bool lockAcquired = lock.TryTimedAcquireScoped(std::chrono::minutes(1), [] {
// enteredCriticalSection++;
// });
// assert(lockAcquired);
//
// Return: true if lock was acquired.
template<class FunctionType>
FORCE_INLINE bool TryTimedAcquireScoped(const timeout_ms timeoutInMilliseconds, const FunctionType& func)
{
if (TryTimedAcquire(timeoutInMilliseconds))
{
ReleaseOnDestroy releaseScope(*this);
func();
return true;
}
return false;
}
private:
class ReleaseOnDestroy
{
public:
FORCE_INLINE ReleaseOnDestroy(ReentrantLock& lockReference) : m_LockReference(lockReference) {}
FORCE_INLINE ~ReleaseOnDestroy() { m_LockReference.Release(); }
private:
ReentrantLock& m_LockReference;
};
Baselib_ReentrantLock m_ReentrantLockData;
};
}
}