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 "../utility/utility.hpp"
14#include "../macros.hpp"
15#include <thread>
16#include <atomic>
17
18hi_export_module(hikogui.concurrency.unfair_recursive_mutex);
19
20hi_export namespace hi { inline namespace v1 {
21
43 /* Thread annotation syntax.
44 *
45 * FIRST - The thread that acquires/acquired the mutex
46 * OWNER - The FIRST thread that recursively requests a lock.
47 * OTHER - Another thread while the mutex is held.
48 */
49
51
52 // FIRST=write, OWNER|OTHER=read
53 std::atomic<thread_id> owner = 0;
54
55 // FIRST=write, OWNER=increment, FIRST|OWNER=decrement
56 uint32_t count = 0;
57
58public:
60 unfair_recursive_mutex& operator=(unfair_recursive_mutex const&) = delete;
61
62 unfair_recursive_mutex() = default;
63 ~unfair_recursive_mutex() = default;
64
70 [[nodiscard]] int recurse_lock_count() const noexcept
71 {
72 // The following load() is:
73 // - valid-and-equal to thread_id when the OWNER has the lock.
74 // - zero or valid-and-not-equal to thread_id when this is an OTHER thread.
75 //
76 // This only works for comparing the owner with the current thread, it would
77 // not work to check the owner with a thread_id of another thread.
78 if (owner.load(std::memory_order::acquire) == current_thread_id()) {
79 return count;
80 } else {
81 return 0;
82 }
83 }
84
88 [[nodiscard]] bool try_lock() noexcept
89 {
90 // FIRST | OWNER | OTHER
91 auto const thread_id = current_thread_id();
92
93 // The following load() is:
94 // - valid-and-equal to thread_id when the OWNER has the lock.
95 // - zero or valid-and-not-equal to thread_id when this is an OTHER thread.
96 //
97 // note: theoretically a relaxed load could be enough, but in C++20 any undefined behaviour causing an out-of-bound
98 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
99 // from the point of view of the optimizer.
100 if (owner.load(std::memory_order::acquire) == thread_id) {
101 // FIRST | OWNER
102 hi_axiom(count != 0);
103 ++count;
104
105 // OWNER
106 return true;
107
108 } else if (mutex.try_lock()) { // OTHER (inside the if expression)
109 // FIRST
110 hi_axiom(count == 0);
111 count = 1;
112 hi_axiom(owner == 0);
113
114 // note: theoretically a relaxed store could be enough, but in C++20 any undefined behaviour causing an out-of-bound
115 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
116 // from the point of view of the optimizer.
117 owner.store(thread_id, std::memory_order::release);
118
119 return true;
120
121 } else {
122 // OTHER
123 return false;
124 }
125 }
126
143 void lock() noexcept
144 {
145 // FIRST | OWNER | OTHER
146 auto const thread_id = current_thread_id();
147
148 // The following load() is:
149 // - valid-and-equal to thread_id when the OWNER has the lock.
150 // - zero or valid-and-not-equal to thread_id when this is an OTHER thread.
151 //
152 // note: theoretically a relaxed load could be enough, but in C++20 any undefined behaviour causing an out-of-bound
153 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
154 // from the point of view of the optimizer.
155 if (owner.load(std::memory_order::acquire) == thread_id) {
156 // FIRST | OWNER
157 hi_axiom(count != 0);
158 ++count;
159
160 // OWNER
161
162 } else {
163 // OTHER
164 mutex.lock();
165
166 // FIRST
167 hi_axiom(count == 0);
168 count = 1;
169 hi_axiom(owner == 0);
170
171 // note: theoretically a relaxed store could be enough, but in C++20 any undefined behaviour causing an out-of-bound
172 // array access inside the critical section protected by the unfair_recursive_mutex will overwrite owner
173 // from the point of view of the optimizer.
174 owner.store(thread_id, std::memory_order::release);
175 }
176 }
177
178 void unlock() noexcept
179 {
180 // FIRST | OWNER
181
182 // Unlock must be called on the thread that locked the mutex
183 hi_axiom(recurse_lock_count() != 0);
184
185 if (--count == 0) {
186 // FIRST
187
188 // Only OTHER can execute in `lock()` or `try_lock()`,
189 // where it will either see the thread_id of FIRST or zero.
190 // In both cases the OTHER thread is detected correctly.
191 owner.store(0, std::memory_order::release);
192
193 mutex.unlock();
194 // OTHER
195 }
196 // OWNER | OTHER
197 }
198};
199
200}} // 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.
The HikoGUI namespace.
Definition array_generic.hpp:20
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
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:216
An unfair recursive-mutex This is a fast implementation of a recursive-mutex which does not fairly ar...
Definition unfair_recursive_mutex.hpp:42
void lock() noexcept
Definition unfair_recursive_mutex.hpp:143
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:88
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:70
T load(T... args)
T store(T... args)