HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
unfair_recursive_mutex.hpp
Go to the documentation of this file.
1// Copyright Take Vos 2020-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
9#pragma once
10
11#include "unfair_mutex.hpp"
12#include "thread.hpp"
13#include <thread>
14
15namespace hi { inline namespace v1 {
16
38 /* Thread annotation syntax.
39 *
40 * FIRST - The thread that acquires/acquired the mutex
41 * OWNER - The FIRST thread that recursively requests a lock.
42 * OTHER - Another thread while the mutex is held.
43 */
44
46
47 // FIRST=write, OWNER|OTHER=read
48 std::atomic<thread_id> owner = 0;
49
50 // FIRST=write, OWNER=increment, FIRST|OWNER=decrement
51 uint32_t count = 0;
52
53public:
55 unfair_recursive_mutex& operator=(unfair_recursive_mutex const&) = delete;
56
57 unfair_recursive_mutex() = default;
58 ~unfair_recursive_mutex() = default;
59
65 [[nodiscard]] int recurse_lock_count() const noexcept
66 {
67 // The following load() is:
68 // - valid-and-equal to thread_id when the OWNER has the lock.
69 // - zero or valid-and-not-equal to thread_id when this is an OTHER thread.
70 //
71 // This only works for comparing the owner with the current thread, it would
72 // not work to check the owner with a thread_id of another thread.
73 if (owner.load(std::memory_order::acquire) == current_thread_id()) {
74 return count;
75 } else {
76 return 0;
77 }
78 }
79
83 [[nodiscard]] bool try_lock() noexcept
84 {
85 // FIRST | OWNER | OTHER
86 hilet thread_id = current_thread_id();
87
88 // The following load() is:
89 // - valid-and-equal to thread_id when the OWNER has the lock.
90 // - zero or valid-and-not-equal to thread_id when this is an OTHER thread.
91 //
92 // note: theoretically a relaxed load could be enough, but in C++20 any undefined behaviour causing an out-of-bound
93 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
94 // from the point of view of the optimizer.
95 if (owner.load(std::memory_order::acquire) == thread_id) {
96 // FIRST | OWNER
97 hi_axiom(count != 0);
98 ++count;
99
100 // OWNER
101 return true;
102
103 } else if (mutex.try_lock()) { // OTHER (inside the if expression)
104 // FIRST
105 hi_axiom(count == 0);
106 count = 1;
107 hi_axiom(owner == 0);
108
109 // note: theoretically a relaxed store could be enough, but in C++20 any undefined behaviour causing an out-of-bound
110 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
111 // from the point of view of the optimizer.
112 owner.store(thread_id, std::memory_order::release);
113
114 return true;
115
116 } else {
117 // OTHER
118 return false;
119 }
120 }
121
138 void lock() noexcept
139 {
140 // FIRST | OWNER | OTHER
141 hilet thread_id = current_thread_id();
142
143 // The following load() is:
144 // - valid-and-equal to thread_id when the OWNER has the lock.
145 // - zero or valid-and-not-equal to thread_id when this is an OTHER thread.
146 //
147 // note: theoretically a relaxed load could be enough, but in C++20 any undefined behaviour causing an out-of-bound
148 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
149 // from the point of view of the optimizer.
150 if (owner.load(std::memory_order::acquire) == thread_id) {
151 // FIRST | OWNER
152 hi_axiom(count != 0);
153 ++count;
154
155 // OWNER
156
157 } else {
158 // OTHER
159 mutex.lock();
160
161 // FIRST
162 hi_axiom(count == 0);
163 count = 1;
164 hi_axiom(owner == 0);
165
166 // note: theoretically a relaxed store could be enough, but in C++20 any undefined behaviour causing an out-of-bound
167 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
168 // from the point of view of the optimizer.
169 owner.store(thread_id, std::memory_order::release);
170 }
171 }
172
173 void unlock() noexcept
174 {
175 // FIRST | OWNER
176
177 // Unlock must be called on the thread that locked the mutex
179
180 if (--count == 0) {
181 // FIRST
182
183 // Only OTHER can execute in `lock()` or `try_lock()`,
184 // where it will either see the thread_id of FIRST or zero.
185 // In both cases the OTHER thread is detected correctly.
186 owner.store(0, std::memory_order::release);
187
188 mutex.unlock();
189 // OTHER
190 }
191 // OWNER | OTHER
192 }
193};
194
195}} // namespace hi::v1
Functions and types for accessing operating system threads.
Definition of the unfair_mutex.
#define hi_axiom(expression,...)
Specify an axiom; an expression that is true.
Definition assert.hpp:253
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
thread_id current_thread_id() noexcept
Get the current thread id.
Definition thread.hpp:44
DOXYGEN BUG.
Definition algorithm.hpp:13
geometry/margins.hpp
Definition cache.hpp:11
An unfair mutex This is a fast implementation of a mutex which does not fairly arbitrate between mult...
Definition unfair_mutex.hpp:36
bool try_lock() noexcept
When try_lock() is called from a thread that already owns the lock it will return false.
Definition unfair_mutex.hpp:84
An unfair recursive-mutex This is a fast implementation of a recursive-mutex which does not fairly ar...
Definition unfair_recursive_mutex.hpp:37
void lock() noexcept
Definition unfair_recursive_mutex.hpp:138
bool try_lock() noexcept
When try_lock() is called on a thread that already holds the lock true is returned.
Definition unfair_recursive_mutex.hpp:83
int recurse_lock_count() const noexcept
This function should be used in hi_axiom() to check if the lock is held by current thread.
Definition unfair_recursive_mutex.hpp:65
T load(T... args)
T store(T... args)