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 "../macros.hpp"
14#include <thread>
15
16namespace hi { inline namespace v1 {
17
39 /* Thread annotation syntax.
40 *
41 * FIRST - The thread that acquires/acquired the mutex
42 * OWNER - The FIRST thread that recursively requests a lock.
43 * OTHER - Another thread while the mutex is held.
44 */
45
47
48 // FIRST=write, OWNER|OTHER=read
49 std::atomic<thread_id> owner = 0;
50
51 // FIRST=write, OWNER=increment, FIRST|OWNER=decrement
52 uint32_t count = 0;
53
54public:
56 unfair_recursive_mutex& operator=(unfair_recursive_mutex const&) = delete;
57
58 unfair_recursive_mutex() = default;
59 ~unfair_recursive_mutex() = default;
60
67 {
68 // The following load() is:
69 // - valid-and-equal to thread_id when the OWNER has the lock.
70 // - zero or valid-and-not-equal to thread_id when this is an OTHER thread.
71 //
72 // This only works for comparing the owner with the current thread, it would
73 // not work to check the owner with a thread_id of another thread.
74 if (owner.load(std::memory_order::acquire) == current_thread_id()) {
75 return count;
76 } else {
77 return 0;
78 }
79 }
80
85 {
86 // FIRST | OWNER | OTHER
87 hilet thread_id = current_thread_id();
88
89 // The following load() is:
90 // - valid-and-equal to thread_id when the OWNER has the lock.
91 // - zero or valid-and-not-equal to thread_id when this is an OTHER thread.
92 //
93 // note: theoretically a relaxed load could be enough, but in C++20 any undefined behaviour causing an out-of-bound
94 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
95 // from the point of view of the optimizer.
96 if (owner.load(std::memory_order::acquire) == thread_id) {
97 // FIRST | OWNER
98 hi_axiom(count != 0);
99 ++count;
100
101 // OWNER
102 return true;
103
104 } else if (mutex.try_lock()) { // OTHER (inside the if expression)
105 // FIRST
106 hi_axiom(count == 0);
107 count = 1;
108 hi_axiom(owner == 0);
109
110 // note: theoretically a relaxed store could be enough, but in C++20 any undefined behaviour causing an out-of-bound
111 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
112 // from the point of view of the optimizer.
113 owner.store(thread_id, std::memory_order::release);
114
115 return true;
116
117 } else {
118 // OTHER
119 return false;
120 }
121 }
122
140 {
141 // FIRST | OWNER | OTHER
142 hilet thread_id = current_thread_id();
143
144 // The following load() is:
145 // - valid-and-equal to thread_id when the OWNER has the lock.
146 // - zero or valid-and-not-equal to thread_id when this is an OTHER thread.
147 //
148 // note: theoretically a relaxed load could be enough, but in C++20 any undefined behaviour causing an out-of-bound
149 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
150 // from the point of view of the optimizer.
151 if (owner.load(std::memory_order::acquire) == thread_id) {
152 // FIRST | OWNER
153 hi_axiom(count != 0);
154 ++count;
155
156 // OWNER
157
158 } else {
159 // OTHER
160 mutex.lock();
161
162 // FIRST
163 hi_axiom(count == 0);
164 count = 1;
165 hi_axiom(owner == 0);
166
167 // note: theoretically a relaxed store could be enough, but in C++20 any undefined behaviour causing an out-of-bound
168 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
169 // from the point of view of the optimizer.
170 owner.store(thread_id, std::memory_order::release);
171 }
172 }
173
174 void unlock() noexcept
175 {
176 // FIRST | OWNER
177
178 // Unlock must be called on the thread that locked the mutex
179 hi_axiom(recurse_lock_count());
180
181 if (--count == 0) {
182 // FIRST
183
184 // Only OTHER can execute in `lock()` or `try_lock()`,
185 // where it will either see the thread_id of FIRST or zero.
186 // In both cases the OTHER thread is detected correctly.
187 owner.store(0, std::memory_order::release);
188
189 mutex.unlock();
190 // OTHER
191 }
192 // OWNER | OTHER
193 }
194};
195
196}} // namespace hi::v1
Functions and types for accessing operating system threads.
Definition of the unfair_mutex.
thread_id current_thread_id() noexcept
Get the current thread id.
DOXYGEN BUG.
Definition algorithm.hpp:16
geometry/margins.hpp
Definition lookahead_iterator.hpp:5
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
An unfair mutex This is a fast implementation of a mutex which does not fairly arbitrate between mult...
Definition unfair_mutex_intf.hpp:38
bool try_lock() noexcept
When try_lock() is called from a thread that already owns the lock it will return false.
Definition unfair_mutex_impl.hpp:213
An unfair recursive-mutex This is a fast implementation of a recursive-mutex which does not fairly ar...
Definition unfair_recursive_mutex.hpp:38
void lock() noexcept
Definition unfair_recursive_mutex.hpp:139
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:84
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:66
T load(T... args)
T store(T... args)