HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
unfair_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 "required.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 tt {
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
41 ~unfair_mutex_impl() requires (UseDeadLockDetector)
42 {
44 }
45
46 ~unfair_mutex_impl() = default;
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 ttlet other = dead_lock_detector::lock(this);
57 tt_axiom(other != this, "Mutex already locked.");
58 tt_axiom(other == nullptr, "Potential dead-lock.");
59 }
60
61 tt_axiom(holds_invariant());
62
63 // Switch to 1 means there are no waiters.
64 semaphore_value_type expected = 0;
65 if (!semaphore.compare_exchange_strong(expected, 1, std::memory_order::acquire)) {
66 [[unlikely]] lock_contended(expected);
67 }
68
69 tt_axiom(holds_invariant());
70 }
71
79 [[nodiscard]] bool try_lock() noexcept {
80 if constexpr (UseDeadLockDetector) {
81 ttlet other = dead_lock_detector::lock(this);
82 tt_axiom(other != this, "Mutex already locked.");
83 tt_axiom(other == nullptr, "Potential dead-lock.");
84 }
85
86 tt_axiom(holds_invariant());
87
88 // Switch to 1 means there are no waiters.
89 semaphore_value_type expected = 0;
90 if (!semaphore.compare_exchange_strong(expected, 1, std::memory_order::acquire)) {
91 tt_axiom(holds_invariant());
92
93 if constexpr (UseDeadLockDetector) {
94 tt_axiom(dead_lock_detector::unlock(this), "Unlocking mutex out of order.");
95 }
96
97 [[unlikely]] return false;
98 }
99
100 tt_axiom(holds_invariant());
101 return true;
102 }
103
104 void unlock() noexcept {
105 if constexpr (UseDeadLockDetector) {
106 tt_axiom(dead_lock_detector::unlock(this), "Unlocking mutex out of order.");
107 }
108
109 tt_axiom(holds_invariant());
110
111 if (semaphore.fetch_sub(1, std::memory_order::relaxed) != 1) {
112 [[unlikely]] semaphore.store(0, std::memory_order::release);
113
114 semaphore.notify_one();
115 } else {
116 atomic_thread_fence(std::memory_order::release);
117 }
118
119 tt_axiom(holds_invariant());
120 }
121
122private:
123 /*
124 * semaphore value:
125 * 0 - Unlocked, no other thread is waiting.
126 * 1 - Locked, no other thread is waiting.
127 * 2 - Locked, zero or more threads are waiting.
128 */
129 std::atomic_unsigned_lock_free semaphore = 0;
130 using semaphore_value_type = typename decltype(semaphore)::value_type;
131
132 bool holds_invariant() const noexcept
133 {
134 return semaphore.load(std::memory_order::relaxed) <= 2;
135 }
136
137 tt_no_inline void lock_contended(semaphore_value_type expected) noexcept
138 {
139 tt_axiom(holds_invariant());
140
141 do {
142 ttlet should_wait = expected == 2;
143
144 // Set to 2 when we are waiting.
145 expected = 1;
146 if (should_wait || semaphore.compare_exchange_strong(expected, 2)) {
147 tt_axiom(holds_invariant());
148 semaphore.wait(2);
149 }
150
151 tt_axiom(holds_invariant());
152 // Set to 2 when acquiring the lock, so that during unlock we wake other waiting threads.
153 expected = 0;
154 } while (!semaphore.compare_exchange_strong(expected, 2));
155 }
156};
157
158#if TT_BUILD_TYPE == TT_BT_DEBUG
159using unfair_mutex = unfair_mutex_impl<true>;
160#else
161using unfair_mutex = unfair_mutex_impl<false>;
162#endif
163
164}
static void * lock(void *object) noexcept
Lock an object on this thread.
static void remove_object(void *object) noexcept
Remove the object from the detection.
static bool unlock(void *object) noexcept
Unlock an object on this thread.
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:79
T atomic_thread_fence(T... args)