1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
// Copyright (C) 2015-2025 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#ifndef FOONATHAN_MEMORY_THREADING_HPP_INCLUDED
#define FOONATHAN_MEMORY_THREADING_HPP_INCLUDED
/// \file
/// The mutex types.
#include <type_traits>
#include "allocator_traits.hpp"
#include "config.hpp"
#if FOONATHAN_HOSTED_IMPLEMENTATION
#include <mutex>
#endif
namespace foonathan
{
namespace memory
{
/// A dummy \c Mutex class that does not lock anything.
/// It is a valid \c Mutex and can be used to disable locking anywhere a \c Mutex is requested.
/// \ingroup core
struct no_mutex
{
void lock() noexcept {}
bool try_lock() noexcept
{
return true;
}
void unlock() noexcept {}
};
/// Specifies whether or not a \concept{concept_rawallocator,RawAllocator} is thread safe as-is.
/// This allows to use \ref no_mutex as an optimization.
/// Note that stateless allocators are implictly thread-safe.
/// Specialize it only for your own stateful allocators.
/// \ingroup core
template <class RawAllocator>
struct is_thread_safe_allocator
: std::integral_constant<bool, !allocator_traits<RawAllocator>::is_stateful::value>
{
};
namespace detail
{
// selects a mutex for an Allocator
// stateless allocators don't need locking
template <class RawAllocator, class Mutex>
using mutex_for =
typename std::conditional<is_thread_safe_allocator<RawAllocator>::value, no_mutex,
Mutex>::type;
// storage for mutexes to use EBO
// it provides const lock/unlock function, inherit from it
template <class Mutex>
class mutex_storage
{
public:
mutex_storage() noexcept = default;
mutex_storage(const mutex_storage&) noexcept {}
mutex_storage& operator=(const mutex_storage&) noexcept
{
return *this;
}
void lock() const
{
mutex_.lock();
}
void unlock() const noexcept
{
mutex_.unlock();
}
protected:
~mutex_storage() noexcept = default;
private:
mutable Mutex mutex_;
};
template <>
class mutex_storage<no_mutex>
{
public:
mutex_storage() noexcept = default;
void lock() const noexcept {}
void unlock() const noexcept {}
protected:
~mutex_storage() noexcept = default;
};
// non changeable pointer to an Allocator that keeps a lock
// I don't think EBO is necessary here...
template <class Alloc, class Mutex>
class locked_allocator
{
public:
locked_allocator(Alloc& alloc, Mutex& m) noexcept : mutex_(&m), alloc_(&alloc)
{
mutex_->lock();
}
locked_allocator(locked_allocator&& other) noexcept
: mutex_(other.mutex_), alloc_(other.alloc_)
{
other.mutex_ = nullptr;
other.alloc_ = nullptr;
}
~locked_allocator() noexcept
{
if (mutex_)
mutex_->unlock();
}
locked_allocator& operator=(locked_allocator&& other) noexcept = delete;
Alloc& operator*() const noexcept
{
FOONATHAN_MEMORY_ASSERT(alloc_);
return *alloc_;
}
Alloc* operator->() const noexcept
{
FOONATHAN_MEMORY_ASSERT(alloc_);
return alloc_;
}
private:
Mutex* mutex_; // don't use unqiue_lock to avoid dependency
Alloc* alloc_;
};
template <class Alloc, class Mutex>
locked_allocator<Alloc, Mutex> lock_allocator(Alloc& a, Mutex& m)
{
return {a, m};
}
} // namespace detail
} // namespace memory
} // namespace foonathan
#endif // FOONATHAN_MEMORY_THREADING_HPP_INCLUDED
|