util/SingleWriterValueList.h (77 lines of code) (raw):
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <forward_list>
#include <memory>
#include <mutex>
#include <stack>
#include <type_traits>
#include <utility>
#ifndef YOGA_EXPORT
#ifdef _MSC_VER
#define YOGA_EXPORT
#else
#define YOGA_EXPORT __attribute__((visibility("default")))
#endif
#endif
namespace facebook {
namespace yoga {
namespace detail {
class YOGA_EXPORT FreeList {
std::stack<void*> free_;
void* getRaw();
public:
FreeList();
~FreeList();
void put(std::mutex&, void*);
template <typename T>
T* get() {
return static_cast<T*>(getRaw());
}
};
} // namespace detail
/// SingleWriterValueList is a data structure that holds a list of values. Each
/// value can be borrowed for exclusive writing, and will not be exposed to
/// another borrower until returned.
/// Additionaly, the whole list of values can be accessed for reading via const
/// iterators. Read consistency depends on CPU internals, i.e. whether values
/// are written to memory atomically.
///
/// A typical usage scenario would be a set of threads, where each thread
/// borrows a value for lock free writing, e.g. as a thread local variable. This
/// avoids the usage of atomics, or locking of shared memory, which both can
/// lead to increased latency due to CPU cache flushes and waits.
///
/// Values are heap allocated (via forward_list), which typically will avoid
/// multiple values being allocated in the same CPU cache line, which would also
/// lead to cache flushing.
///
/// SingleWriterValueList never deallocates, to guarantee the validity of
/// references and iterators. However, memory returned by a borrower can be
/// borrowed again.
///
/// SingleWriterValueList supports return policies as second template parameter,
/// i.e. an optional mutation of values after a borrower returns them. The
/// default policy is to do nothing. SingleWriterValueList::resetPolicy is a
/// convenience method that will move assign the default value of a type.
///
/// Example:
///
/// static SingleWriterValueList<int> counters;
/// thread_local auto localCounter = counters.borrow();
///
/// /* per thread */
/// localCounter =+ n;
///
/// /* anywhere */
/// std::accumulate(counters.begin(), counters.end(), 0);
///
template <typename T, void (*ReturnPolicy)(T&) = nullptr>
class YOGA_EXPORT SingleWriterValueList {
std::forward_list<T> values_{};
std::mutex acquireMutex_{};
detail::FreeList freeValuesList_{};
T* allocValue() {
values_.emplace_front();
return &values_.front();
}
void returnRef(T* value) {
if (ReturnPolicy != nullptr) {
ReturnPolicy(*value);
}
freeValuesList_.put(acquireMutex_, value);
}
public:
using const_iterator = decltype(values_.cbegin());
/// RAII representation of a single value, borrowed for exclusive writing.
/// Instances cannot be copied, and will return the borrowed value to the
/// owner upon destruction.
class Borrowed {
T* value_;
SingleWriterValueList* owner_;
public:
Borrowed(T* value, SingleWriterValueList* owner)
: value_{value}, owner_{owner} {}
~Borrowed() {
if (owner_ != nullptr && value_ != nullptr) {
owner_->returnRef(value_);
}
}
Borrowed(Borrowed&& other) = default;
Borrowed& operator=(Borrowed&& other) = default;
// no copies allowed
Borrowed(const Borrowed&) = delete;
Borrowed& operator=(const Borrowed&) = delete;
T& get() { return *value_; }
T& operator*() { return get(); }
};
Borrowed borrow() {
std::lock_guard<std::mutex> lock{acquireMutex_};
T* value = freeValuesList_.get<T>();
return {value != nullptr ? value : allocValue(), this};
}
const_iterator cbegin() const { return values_.cbegin(); };
const_iterator cend() const { return values_.cend(); };
const_iterator begin() const { return cbegin(); };
const_iterator end() const { return cend(); };
static void resetPolicy(T& value) { value = std::move(T{}); }
};
} // namespace yoga
} // namespace facebook