HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
unfair_mutex.hpp
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
5#pragma once
6
7#include "utility.hpp"
8#include "thread.hpp"
9#include "assert.hpp"
10#include "dead_lock_detector.hpp"
11#include <atomic>
12#include <memory>
13#include <thread>
14
15namespace hi::inline v1 {
16
32template<bool UseDeadLockDetector>
34public:
35 constexpr unfair_mutex_impl() noexcept {}
36 unfair_mutex_impl(unfair_mutex_impl const&) = delete;
38 unfair_mutex_impl& operator=(unfair_mutex_impl const&) = delete;
39 unfair_mutex_impl& operator=(unfair_mutex_impl&&) = delete;
40
42 {
43 if constexpr (UseDeadLockDetector) {
44 dead_lock_detector::remove_object(this);
45 }
46 }
47
48 bool is_locked() const noexcept
49 {
50 return semaphore.load(std::memory_order::relaxed) != 0;
51 }
52
53 void lock() noexcept
54 {
55 if constexpr (UseDeadLockDetector) {
56 hilet other = dead_lock_detector::lock(this);
57 // *this mutex is already locked.
58 hi_axiom(other != this);
59 // Potential dead-lock because of different ordering with other.
60 hi_axiom(other == nullptr);
61 }
62
63 hi_axiom(holds_invariant());
64
65 // Switch to 1 means there are no waiters.
66 semaphore_value_type expected = 0;
67 if (!semaphore.compare_exchange_strong(expected, 1, std::memory_order::acquire)) {
68 [[unlikely]] lock_contended(expected);
69 }
70
71 hi_axiom(holds_invariant());
72 }
73
81 [[nodiscard]] bool try_lock() noexcept
82 {
83 if constexpr (UseDeadLockDetector) {
84 hilet other = dead_lock_detector::lock(this);
85 // *this mutex is already locked.
86 hi_axiom(other != this);
87 // Potential dead-lock because of different ordering with other.
88 hi_axiom(other == nullptr);
89 }
90
91 hi_axiom(holds_invariant());
92
93 // Switch to 1 means there are no waiters.
94 semaphore_value_type expected = 0;
95 if (!semaphore.compare_exchange_strong(expected, 1, std::memory_order::acquire)) {
96 hi_axiom(holds_invariant());
97
98 if constexpr (UseDeadLockDetector) {
99 // *this mutex is locked out-of-order from the order of being locked.
100 hi_axiom(dead_lock_detector::unlock(this));
101 }
102
103 [[unlikely]] return false;
104 }
105
106 hi_axiom(holds_invariant());
107 return true;
108 }
109
110 void unlock() noexcept
111 {
112 if constexpr (UseDeadLockDetector) {
113 // *this mutex is locked out-of-order from the order of being locked.
114 hi_axiom(dead_lock_detector::unlock(this));
115 }
116
117 hi_axiom(holds_invariant());
118
119 if (semaphore.fetch_sub(1, std::memory_order::relaxed) != 1) {
120 [[unlikely]] semaphore.store(0, std::memory_order::release);
121
122 semaphore.notify_one();
123 } else {
124 atomic_thread_fence(std::memory_order::release);
125 }
126
127 hi_axiom(holds_invariant());
128 }
129
130private:
131 /*
132 * semaphore value:
133 * 0 - Unlocked, no other thread is waiting.
134 * 1 - Locked, no other thread is waiting.
135 * 2 - Locked, zero or more threads are waiting.
136 */
137 std::atomic_unsigned_lock_free semaphore = 0;
138 using semaphore_value_type = typename decltype(semaphore)::value_type;
139
140 bool holds_invariant() const noexcept
141 {
142 return semaphore.load(std::memory_order::relaxed) <= 2;
143 }
144
145 hi_no_inline void lock_contended(semaphore_value_type expected) noexcept
146 {
147 hi_axiom(holds_invariant());
148
149 do {
150 hilet should_wait = expected == 2;
151
152 // Set to 2 when we are waiting.
153 expected = 1;
154 if (should_wait || semaphore.compare_exchange_strong(expected, 2)) {
155 hi_axiom(holds_invariant());
156 semaphore.wait(2);
157 }
158
159 hi_axiom(holds_invariant());
160 // Set to 2 when acquiring the lock, so that during unlock we wake other waiting threads.
161 expected = 0;
162 } while (!semaphore.compare_exchange_strong(expected, 2));
163 }
164};
165
166#ifndef NDEBUG
167using unfair_mutex = unfair_mutex_impl<true>;
168#else
169using unfair_mutex = unfair_mutex_impl<false>;
170#endif
171
172} // namespace hi::inline v1
Utilities used by the HikoGUI library itself.
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
DOXYGEN BUG.
Definition algorithm.hpp:15
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
T atomic_thread_fence(T... args)