template<class T, class Tag=void>
class ThreadLocal {
public:
- ThreadLocal() = default;
+ constexpr ThreadLocal() {}
T* get() const {
T* ptr = tlp_.get();
template<class T, class Tag=void>
class ThreadLocalPtr {
+ private:
+ typedef threadlocal_detail::StaticMeta<Tag> StaticMeta;
public:
- ThreadLocalPtr() : id_(threadlocal_detail::StaticMeta<Tag>::create()) { }
+ constexpr ThreadLocalPtr() : id_() {}
- ThreadLocalPtr(ThreadLocalPtr&& other) noexcept : id_(other.id_) {
- other.id_ = 0;
+ ThreadLocalPtr(ThreadLocalPtr&& other) noexcept :
+ id_(std::move(other.id_)) {
}
ThreadLocalPtr& operator=(ThreadLocalPtr&& other) {
assert(this != &other);
destroy();
- id_ = other.id_;
- other.id_ = 0;
+ id_ = std::move(other.id_);
return *this;
}
}
T* get() const {
- return static_cast<T*>(threadlocal_detail::StaticMeta<Tag>::get(id_).ptr);
+ threadlocal_detail::ElementWrapper& w = StaticMeta::get(&id_);
+ return static_cast<T*>(w.ptr);
}
T* operator->() const {
}
T* release() {
- threadlocal_detail::ElementWrapper& w =
- threadlocal_detail::StaticMeta<Tag>::get(id_);
+ threadlocal_detail::ElementWrapper& w = StaticMeta::get(&id_);
return static_cast<T*>(w.release());
}
void reset(T* newPtr = nullptr) {
- threadlocal_detail::ElementWrapper& w =
- threadlocal_detail::StaticMeta<Tag>::get(id_);
+ threadlocal_detail::ElementWrapper& w = StaticMeta::get(&id_);
+
if (w.ptr != newPtr) {
w.dispose(TLPDestructionMode::THIS_THREAD);
w.set(newPtr);
*/
template <class Deleter>
void reset(T* newPtr, Deleter deleter) {
- threadlocal_detail::ElementWrapper& w =
- threadlocal_detail::StaticMeta<Tag>::get(id_);
+ threadlocal_detail::ElementWrapper& w = StaticMeta::get(&id_);
if (w.ptr != newPtr) {
w.dispose(TLPDestructionMode::THIS_THREAD);
w.set(newPtr, deleter);
Accessor accessAllThreads() const {
static_assert(!std::is_same<Tag, void>::value,
"Must use a unique Tag to use the accessAllThreads feature");
- return Accessor(id_);
+ return Accessor(id_.getOrAllocate());
}
private:
void destroy() {
- if (id_) {
- threadlocal_detail::StaticMeta<Tag>::destroy(id_);
- }
+ StaticMeta::destroy(&id_);
}
// non-copyable
ThreadLocalPtr(const ThreadLocalPtr&) = delete;
ThreadLocalPtr& operator=(const ThreadLocalPtr&) = delete;
- uint32_t id_; // every instantiation has a unique id
+ mutable typename StaticMeta::EntryID id_;
};
} // namespace folly
ThreadEntry* prev;
};
+
+
// Held in a singleton to track our global instances.
// We have one of these per "Tag", by default one for the whole system
// (Tag=void).
// StaticMeta; you can specify multiple Tag types to break that lock.
template <class Tag>
struct StaticMeta {
+ // Represents an ID of a thread local object. Initially set to the maximum
+ // uint. This representation allows us to avoid a branch in accessing TLS data
+ // (because if you test capacity > id if id = maxint then the test will always
+ // fail). It allows us to keep a constexpr constructor and avoid SIOF.
+ class EntryID {
+ public:
+ static constexpr uint32_t kInvalid = std::numeric_limits<uint32_t>::max();
+ std::atomic<uint32_t> value;
+
+ constexpr EntryID() : value(kInvalid) {
+ }
+
+ EntryID(EntryID&& other) noexcept : value(other.value.load()) {
+ other.value = kInvalid;
+ }
+
+ EntryID& operator=(EntryID&& other) {
+ assert(this != &other);
+ value = other.value.load();
+ other.value = kInvalid;
+ return *this;
+ }
+
+ EntryID(const EntryID& other) = delete;
+ EntryID& operator=(const EntryID& other) = delete;
+
+ uint32_t getOrInvalid() {
+ // It's OK for this to be relaxed, even though we're effectively doing
+ // double checked locking in using this value. We only care about the
+ // uniqueness of IDs, getOrAllocate does not modify any other memory
+ // this thread will use.
+ return value.load(std::memory_order_relaxed);
+ }
+
+ uint32_t getOrAllocate() {
+ uint32_t id = getOrInvalid();
+ if (id != kInvalid) {
+ return id;
+ }
+ // The lock inside allocate ensures that a single value is allocated
+ return instance().allocate(this);
+ }
+ };
+
static StaticMeta<Tag>& instance() {
// Leak it on exit, there's only one per process and we don't have to
// worry about synchronization with exiting threads.
#endif
}
- static uint32_t create() {
+ static uint32_t allocate(EntryID* ent) {
uint32_t id;
auto & meta = instance();
std::lock_guard<std::mutex> g(meta.lock_);
+
+ id = ent->value.load();
+ if (id != EntryID::kInvalid) {
+ return id;
+ }
+
if (!meta.freeIds_.empty()) {
id = meta.freeIds_.back();
meta.freeIds_.pop_back();
} else {
id = meta.nextId_++;
}
+
+ uint32_t old_id = ent->value.exchange(id);
+ DCHECK_EQ(old_id, EntryID::kInvalid);
return id;
}
- static void destroy(uint32_t id) {
+ static void destroy(EntryID* ent) {
try {
auto & meta = instance();
// Elements in other threads that use this id.
std::vector<ElementWrapper> elements;
{
std::lock_guard<std::mutex> g(meta.lock_);
+ uint32_t id = ent->value.exchange(EntryID::kInvalid);
+ if (id == EntryID::kInvalid) {
+ return;
+ }
+
for (ThreadEntry* e = meta.head_.next; e != &meta.head_; e = e->next) {
if (id < e->elementsCapacity && e->elements[id].ptr) {
elements.push_back(e->elements[id]);
* Reserve enough space in the ThreadEntry::elements for the item
* @id to fit in.
*/
- static void reserve(uint32_t id) {
+ static void reserve(EntryID* id) {
auto& meta = instance();
ThreadEntry* threadEntry = getThreadEntry();
size_t prevCapacity = threadEntry->elementsCapacity;
+
+ uint32_t idval = id->getOrAllocate();
+ if (prevCapacity > idval) {
+ return;
+ }
// Growth factor < 2, see folly/docs/FBVector.md; + 5 to prevent
// very slow start.
- size_t newCapacity = static_cast<size_t>((id + 5) * 1.7);
+ size_t newCapacity = static_cast<size_t>((idval + 5) * 1.7);
assert(newCapacity > prevCapacity);
ElementWrapper* reallocated = nullptr;
#endif
}
- static ElementWrapper& get(uint32_t id) {
+ static ElementWrapper& get(EntryID* ent) {
ThreadEntry* threadEntry = getThreadEntry();
+ uint32_t id = ent->getOrInvalid();
+ // if id is invalid, it is equal to uint32_t's max value.
+ // x <= max value is always true
if (UNLIKELY(threadEntry->elementsCapacity <= id)) {
- reserve(id);
+ reserve(ent);
+ id = ent->getOrInvalid();
assert(threadEntry->elementsCapacity > id);
}
return threadEntry->elements[id];