UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
IntrusiveMutex.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
6#include "Async/ParkingLot.h"
7#include "Concepts/DecaysTo.h"
8#include "Concepts/Integral.h"
9#include "CoreTypes.h"
10#include "HAL/PlatformProcess.h"
11
12#include <atomic>
13#include <type_traits>
14
15namespace UE
16{
17
18template <typename ParamsType>
19using TIntrusiveMutexStateType_T = std::decay_t<decltype(ParamsType::IsLockedFlag)>;
20
21template <typename ParamsType, typename StateType = TIntrusiveMutexStateType_T<ParamsType>>
22concept CIntrusiveMutexParams = requires
23{
24 requires CIntegral<StateType>;
25
26 // Required: constexpr static StateType IsLockedFlag = ...;
27 // Flag that is set in the state when the mutex is locked.
28 { ParamsType::IsLockedFlag } -> CDecaysTo<StateType>;
29
30 // Required: constexpr static StateType MayHaveWaitingLockFlag = ...;
31 // Flag that is set in the state when a thread may be waiting to lock the mutex.
32 { ParamsType::MayHaveWaitingLockFlag } -> CDecaysTo<StateType>;
33
34 // Optional: constexpr static StateType IsLockedMask = ...;
35 // Mask tested against State to determine if the mutex is locked. Defaults to IsLockedFlag.
36 requires
37 (!requires { ParamsType::IsLockedMask; }) ||
38 requires { { ParamsType::IsLockedMask } -> CDecaysTo<StateType>; };
39
40 // Optional: constexpr static int32 SpinLimit = ...;
41 // Maximum number of times to spin before waiting. Defaults to 40.
42 requires
43 (!requires { ParamsType::SpinLimit; }) ||
44 requires { { ParamsType::SpinLimit } -> CDecaysTo<int32>; };
45
46 // Optional: static const void* GetWaitAddress(std::atomic<StateType>& State);
47 // Returns the address to pass to ParkingLot wait and wake functions. Defaults to &State.
48 requires
49 (!requires { ParamsType::GetWaitAddress; }) ||
50 requires(std::atomic<StateType>& State) { { ParamsType::GetWaitAddress(State) } -> CDecaysTo<const void*>; };
51};
52
59template <CIntrusiveMutexParams ParamsType>
61{
62 TIntrusiveMutex() = delete;
63
65
66 constexpr static StateType IsLockedFlag = ParamsType::IsLockedFlag;
67 constexpr static StateType MayHaveWaitingLockFlag = ParamsType::MayHaveWaitingLockFlag;
68
69 constexpr static StateType IsLockedMask = []
70 {
71 if constexpr (requires { ParamsType::IsLockedMask; })
72 {
73 return ParamsType::IsLockedMask;
74 }
75 else
76 {
77 return ParamsType::IsLockedFlag;
78 }
79 }();
80
81 constexpr static int32 SpinLimit = []
82 {
83 if constexpr (requires { ParamsType::SpinLimit; })
84 {
85 return ParamsType::SpinLimit;
86 }
87 else
88 {
89 return 40;
90 }
91 }();
92
93 static_assert(IsLockedFlag && (IsLockedFlag & (IsLockedFlag - 1)) == 0, "IsLockedFlag must be one bit.");
94 static_assert(MayHaveWaitingLockFlag && (MayHaveWaitingLockFlag & (MayHaveWaitingLockFlag - 1)) == 0, "MayHaveWaitingLockFlag must be one bit.");
95 static_assert(IsLockedFlag != MayHaveWaitingLockFlag, "IsLockedFlag and MayHaveWaitingLockFlag must be different bits.");
96 static_assert((IsLockedMask & IsLockedFlag) == IsLockedFlag, "IsLockedMask must contain IsLockedFlag.");
97 static_assert((IsLockedMask & MayHaveWaitingLockFlag) == 0, "IsLockedMask must not contain MayHaveWaitingLockFlag.");
98 static_assert(SpinLimit >= 0, "SpinLimit must be non-negative.");
99
100 FORCEINLINE static const void* GetWaitAddress(const std::atomic<StateType>& State)
101 {
102 if constexpr (requires { ParamsType::GetWaitAddress; })
103 {
104 return ParamsType::GetWaitAddress(State);
105 }
106 else
107 {
108 return &State;
109 }
110 }
111
112public:
113 [[nodiscard]] FORCEINLINE static bool IsLocked(const std::atomic<StateType>& State)
114 {
115 return !!(State.load(std::memory_order_relaxed) & IsLockedFlag);
116 }
117
118 [[nodiscard]] FORCEINLINE static bool TryLock(std::atomic<StateType>& State)
119 {
120 StateType Expected = State.load(std::memory_order_relaxed);
121 return !(Expected & IsLockedMask) &&
122 State.compare_exchange_strong(Expected, Expected | IsLockedFlag, std::memory_order_acquire, std::memory_order_relaxed);
123 }
124
125 FORCEINLINE static void Lock(std::atomic<StateType>& State)
126 {
127 StateType Expected = State.load(std::memory_order_relaxed) & ~IsLockedMask & ~MayHaveWaitingLockFlag;
128 if (LIKELY(State.compare_exchange_weak(Expected, Expected | IsLockedFlag, std::memory_order_acquire, std::memory_order_relaxed)))
129 {
130 return;
131 }
132 LockSlow(State);
133 }
134
135 FORCEINLINE static void LockLoop(std::atomic<StateType>& State)
136 {
137 int32 SpinCount = 0;
138 for (StateType CurrentState = State.load(std::memory_order_relaxed);;)
139 {
140 // Try to acquire the lock if it was unlocked, even if there are waiting threads.
141 // Acquiring the lock despite the waiting threads means that this lock is not FIFO and thus not fair.
142 if (LIKELY(!(CurrentState & IsLockedMask)))
143 {
144 if (LIKELY(State.compare_exchange_weak(CurrentState, CurrentState | IsLockedFlag, std::memory_order_acquire, std::memory_order_relaxed)))
145 {
146 return;
147 }
148 continue;
149 }
150
151 // Spin up to the spin limit while there are no waiting threads.
152 if (LIKELY(!(CurrentState & MayHaveWaitingLockFlag) && SpinCount < SpinLimit))
153 {
155 ++SpinCount;
156 CurrentState = State.load(std::memory_order_relaxed);
157 continue;
158 }
159
160 // Store that there are waiting threads. Restart if the state has changed since it was loaded.
161 if (LIKELY(!(CurrentState & MayHaveWaitingLockFlag)))
162 {
163 if (UNLIKELY(!State.compare_exchange_weak(CurrentState, CurrentState | MayHaveWaitingLockFlag, std::memory_order_relaxed)))
164 {
165 continue;
166 }
167 CurrentState |= MayHaveWaitingLockFlag;
168 }
169
170 // Do not enter oversubscription during a wait on a mutex since the wait is generally too short
171 // for it to matter and it can worsen performance a lot for heavily contended locks.
173
174 // Wait if the state has not changed. Either way, loop back and try to acquire the lock after trying to wait.
175 ParkingLot::Wait(GetWaitAddress(State), [&State]
176 {
177 const StateType NewState = State.load(std::memory_order_relaxed);
178 return (NewState & IsLockedMask) && (NewState & MayHaveWaitingLockFlag);
179 }, nullptr);
180 CurrentState = State.load(std::memory_order_relaxed);
181 }
182 }
183
184 FORCEINLINE static void Unlock(std::atomic<StateType>& State)
185 {
186 // Unlock immediately to allow other threads to acquire the lock while this thread looks for a thread to wake.
187 const StateType LastState = State.fetch_sub(IsLockedFlag, std::memory_order_release);
188 if (LIKELY(!(LastState & MayHaveWaitingLockFlag)))
189 {
190 return;
191 }
192 UnlockSlow(State);
193 }
194
195 FORCEINLINE static void WakeWaitingThread(std::atomic<StateType>& State)
196 {
197 ParkingLot::WakeOne(GetWaitAddress(State), [&State](ParkingLot::FWakeState WakeState) -> uint64
198 {
199 if (!WakeState.bDidWake)
200 {
201 // Keep the flag until no thread wakes, otherwise shared locks may win before
202 // an exclusive lock has a chance.
203 State.fetch_and(~MayHaveWaitingLockFlag, std::memory_order_relaxed);
204 }
205 return 0;
206 });
207 }
208
209 [[nodiscard]] FORCEINLINE static bool TryWakeWaitingThread(std::atomic<StateType>& State)
210 {
211 bool bDidWake = false;
212 ParkingLot::WakeOne(GetWaitAddress(State), [&State, &bDidWake](ParkingLot::FWakeState WakeState) -> uint64
213 {
214 if (!WakeState.bDidWake)
215 {
216 // Keep the flag until no thread wakes, otherwise shared locks may win before
217 // an exclusive lock has a chance.
218 State.fetch_and(~MayHaveWaitingLockFlag, std::memory_order_relaxed);
219 }
220 bDidWake = WakeState.bDidWake;
221 return 0;
222 });
223 return bDidWake;
224 }
225
226private:
227 FORCENOINLINE static void LockSlow(std::atomic<StateType>& State)
228 {
229 LockLoop(State);
230 }
231
232 FORCENOINLINE static void UnlockSlow(std::atomic<StateType>& State)
233 {
234 WakeWaitingThread(State);
235 }
236};
237
238} // UE
#define FORCENOINLINE
Definition AndroidPlatform.h:142
#define FORCEINLINE
Definition AndroidPlatform.h:140
#define LIKELY(x)
Definition CityHash.cpp:107
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
#define UNLIKELY(x)
Definition Platform.h:857
FPlatformTypes::uint64 uint64
A 64-bit unsigned integer.
Definition Platform.h:1117
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
Definition IntrusiveMutex.h:61
static FORCEINLINE void Unlock(std::atomic< StateType > &State)
Definition IntrusiveMutex.h:184
static FORCEINLINE bool TryLock(std::atomic< StateType > &State)
Definition IntrusiveMutex.h:118
static FORCEINLINE bool IsLocked(const std::atomic< StateType > &State)
Definition IntrusiveMutex.h:113
static FORCEINLINE bool TryWakeWaitingThread(std::atomic< StateType > &State)
Definition IntrusiveMutex.h:209
static FORCEINLINE void LockLoop(std::atomic< StateType > &State)
Definition IntrusiveMutex.h:135
static FORCEINLINE void Lock(std::atomic< StateType > &State)
Definition IntrusiveMutex.h:125
static FORCEINLINE void WakeWaitingThread(std::atomic< StateType > &State)
Definition IntrusiveMutex.h:195
Definition DecaysTo.h:16
Definition Integral.h:13
Definition IntrusiveMutex.h:22
FWaitState Wait(const void *Address, TFunctionWithContext< bool()> CanWait, TFunctionWithContext< void()> BeforeWait)
Definition ParkingLot.h:52
Definition AdvancedWidgetsModule.cpp:13
std::decay_t< decltype(ParamsType::IsLockedFlag)> TIntrusiveMutexStateType_T
Definition IntrusiveMutex.h:19
static void Yield()
Definition GenericPlatformProcess.h:950
Definition ParkingLot.h:30