HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
unfair_recursive_mutex.hpp
1// Copyright Take Vos 2020.
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
5#pragma once
6
7#include "unfair_mutex.hpp"
8#include "thread.hpp"
9#include <thread>
10
11namespace hi::inline v1 {
12
33 /* Thread annotation syntax.
34 *
35 * FIRST - The thread that acquires/acquired the mutex
36 * OWNER - The FIRST thread that recursively requests a lock.
37 * OTHER - Another thread while the mutex is held.
38 */
39
41
42 // FIRST=write, OWNER|OTHER=read
43 std::atomic<thread_id> owner = 0;
44
45 // FIRST=write, OWNER=increment, FIRST|OWNER=decrement
46 uint32_t count = 0;
47
48public:
50 unfair_recursive_mutex &operator=(unfair_recursive_mutex const &) = delete;
51
52 unfair_recursive_mutex() = default;
53 ~unfair_recursive_mutex() = default;
54
60 [[nodiscard]] int recurse_lock_count() const noexcept
61 {
62 // The following load() is:
63 // - valid-and-equal to thread_id when the OWNER has the lock.
64 // - zero or valid-and-not-equal to thread_id when this is an OTHER thread.
65 //
66 // This only works for comparing the owner with the current thread, it would
67 // not work to check the owner with a thread_id of another thread.
68 if (owner.load(std::memory_order::acquire) == current_thread_id()) {
69 return count;
70 } else {
71 return 0;
72 }
73 }
74
78 [[nodiscard]] bool try_lock() noexcept
79 {
80 // FIRST | OWNER | OTHER
81 hilet thread_id = current_thread_id();
82
83 // The following load() is:
84 // - valid-and-equal to thread_id when the OWNER has the lock.
85 // - zero or valid-and-not-equal to thread_id when this is an OTHER thread.
86 //
87 // note: theoretically a relaxed load could be enough, but in C++20 any undefined behaviour causing an out-of-bound
88 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
89 // from the point of view of the optimizer.
90 if (owner.load(std::memory_order::acquire) == thread_id) {
91 // FIRST | OWNER
92 hi_axiom(count != 0);
93 ++count;
94
95 // OWNER
96 return true;
97
98 } else if (mutex.try_lock()) { // OTHER (inside the if expression)
99 // FIRST
100 hi_axiom(count == 0);
101 count = 1;
102 hi_axiom(owner == 0);
103
104 // note: theoretically a relaxed store could be enough, but in C++20 any undefined behaviour causing an out-of-bound
105 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
106 // from the point of view of the optimizer.
107 owner.store(thread_id, std::memory_order::release);
108
109 return true;
110
111 } else {
112 // OTHER
113 return false;
114 }
115 }
116
133 void lock() noexcept
134 {
135 // FIRST | OWNER | OTHER
136 hilet thread_id = current_thread_id();
137
138 // The following load() is:
139 // - valid-and-equal to thread_id when the OWNER has the lock.
140 // - zero or valid-and-not-equal to thread_id when this is an OTHER thread.
141 //
142 // note: theoretically a relaxed load could be enough, but in C++20 any undefined behaviour causing an out-of-bound
143 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
144 // from the point of view of the optimizer.
145 if (owner.load(std::memory_order::acquire) == thread_id) {
146 // FIRST | OWNER
147 hi_axiom(count != 0);
148 ++count;
149
150 // OWNER
151
152 } else {
153 // OTHER
154 mutex.lock();
155
156 // FIRST
157 hi_axiom(count == 0);
158 count = 1;
159 hi_axiom(owner == 0);
160
161 // note: theoretically a relaxed store could be enough, but in C++20 any undefined behaviour causing an out-of-bound
162 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
163 // from the point of view of the optimizer.
164 owner.store(thread_id, std::memory_order::release);
165 }
166 }
167
168 void unlock() noexcept
169 {
170 // FIRST | OWNER
171
172 // Unlock must be called on the thread that locked the mutex
173 hi_axiom(recurse_lock_count());
174
175 if (--count == 0) {
176 // FIRST
177
178 // Only OTHER can execute in `lock()` or `try_lock()`,
179 // where it will either see the thread_id of FIRST or zero.
180 // In both cases the OTHER thread is detected correctly.
181 owner.store(0, std::memory_order::release);
182
183 mutex.unlock();
184 // OTHER
185 }
186 // OWNER | OTHER
187 }
188};
189
190} // namespace hi::inline v1
#define hilet
Invariant should be the default for variables.
Definition required.hpp:23
An unfair mutex This is a fast implementation of a mutex which does not fairly arbitrate between mult...
Definition unfair_mutex.hpp:33
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:81
An unfair recursive-mutex This is a fast implementation of a recursive-mutex which does not fairly ar...
Definition unfair_recursive_mutex.hpp:32
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:60
void lock() noexcept
Definition unfair_recursive_mutex.hpp:133
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:78
T load(T... args)
T store(T... args)