summaryrefslogtreecommitdiffstats
path: root/library/cpp/threading/thread_local
diff options
context:
space:
mode:
authorkulikov <[email protected]>2025-11-25 01:07:59 +0300
committerkulikov <[email protected]>2025-11-25 01:25:22 +0300
commitf688bd5f5a6b127269d329a997ff5945cbcd5d87 (patch)
tree15564ded86e7d8f8a8f2b59055cca0269d6984ee /library/cpp/threading/thread_local
parent7af73a3de3b9a9faa4a8654f5a2b62adf21dded5 (diff)
TThreadLocalValue over standard thread_locals
Current TThreadLocal implementation doesn't destroy objects on thread exit (so we can't replace thread\_local tls value with it, and with TGenericLocalValue too), standard thread\_local values can't be class members. So try to reimplement TThreadLocalValue: - instead of adressing by thread id, address objects by uniq object id in thread\_local deque of all objects of current type; - use hashmap of all existing perthread deques to destroy objects with their TThreadLocal; - try to make object ids flat; - no complex lookup inside and no contention between tls.Get calls; - ShortLivedThreads ut now works; - some fixes in Trace ut, make threads outlive tls value; - benchmark results <https://nda.ya.ru/t/agWa9PW67NYAAK>, looks better than all others; - probably should switch to this implementation by default and remove old. commit_hash:262ce4d363751f577373a452a0c2c84d1fe5b79c
Diffstat (limited to 'library/cpp/threading/thread_local')
-rw-r--r--library/cpp/threading/thread_local/thread_local.h122
1 files changed, 122 insertions, 0 deletions
diff --git a/library/cpp/threading/thread_local/thread_local.h b/library/cpp/threading/thread_local/thread_local.h
index 15cb43d37f3..af4d7a07cf2 100644
--- a/library/cpp/threading/thread_local/thread_local.h
+++ b/library/cpp/threading/thread_local/thread_local.h
@@ -1,6 +1,8 @@
#pragma once
+#include <util/generic/deque.h>
#include <util/generic/hash.h>
+#include <util/generic/hash_set.h>
#include <util/generic/maybe.h>
#include <util/generic/ptr.h>
#include <util/generic/vector.h>
@@ -91,6 +93,7 @@ enum class EThreadLocalImpl {
HotSwap,
SkipList,
ForwardList,
+ StdThreadLocal // completely different implementation over thread_local keyword, correctly destroys objects on thread finish
};
namespace NDetail {
@@ -122,6 +125,125 @@ private:
mutable std::array<TStorage, NumShards> Shards_;
};
+template <typename T, size_t NumShards>
+class TThreadLocalValue<T, EThreadLocalImpl::StdThreadLocal, NumShards> : private TNonCopyable {
+public:
+ ~TThreadLocalValue() {
+ AllValues_.Destroy(ObjectId_);
+ FlatIds.Put(ObjectId_);
+ }
+
+ template <typename ...ConstructArgs>
+ T& GetRef(ConstructArgs&& ...args) const {
+ return *Get(std::forward<ConstructArgs>(args)...);
+ }
+
+ template <typename ...ConstructArgs>
+ T* Get(ConstructArgs&& ...args) const {
+ auto& values = Values_.Storage;
+
+ with_lock (Values_.Lock) {
+ if (ObjectId_ >= values.size()) {
+ values.resize(ObjectId_ + 1);
+ }
+ }
+
+ TMaybe<T>& v = values[ObjectId_];
+ if (!v.Defined()) {
+ v.ConstructInPlace(std::forward<ConstructArgs>(args)...);
+ }
+
+ return &*v;
+ }
+private:
+ struct TAllValues;
+
+ struct TPerThreadValues {
+ TAdaptiveLock Lock;
+ TDeque<TMaybe<T>> Storage;
+ public:
+ explicit TPerThreadValues(TAllValues* allValues)
+ : AllValues_(allValues)
+ {
+ AllValues_->Add(this);
+ }
+
+ ~TPerThreadValues() {
+ AllValues_->Remove(this);
+ }
+ private:
+ TAllValues* const AllValues_;
+ };
+
+ class TFlatIdGenerator {
+ public:
+ ui32 Get() {
+ with_lock (Lock_) {
+ if (Free_.empty()) {
+ return MaxUnused_++;
+ } else {
+ ui32 free = Free_.back();
+ Free_.pop_back();
+ return free;
+ }
+ }
+ }
+
+ void Put(ui32 id) {
+ with_lock (Lock_) {
+ Free_.push_back(id);
+ }
+ }
+ private:
+ TAdaptiveLock Lock_;
+ TVector<ui32> Free_;
+ ui32 MaxUnused_ = 0;
+ };
+
+ struct TAllValues {
+ public:
+ void Add(TPerThreadValues* v) {
+ with_lock (Lock_) {
+ Ptrs_.insert(v);
+ }
+ }
+
+ void Remove(TPerThreadValues* v) {
+ with_lock (Lock_) {
+ Ptrs_.erase(v);
+ }
+ }
+
+ void Destroy(ui32 objectId) {
+ with_lock (Lock_) {
+ for (auto* v : Ptrs_) {
+ TMaybe<T>* toDestroy = nullptr;
+ with_lock (v->Lock) {
+ if (objectId < v->Storage.size()) {
+ toDestroy = &v->Storage[objectId];
+ }
+ }
+
+ if (toDestroy) {
+ toDestroy->Clear();
+ }
+ }
+ }
+ }
+ private:
+ TAdaptiveLock Lock_;
+ THashSet<TPerThreadValues*> Ptrs_;
+ };
+
+private:
+ static inline TFlatIdGenerator FlatIds;
+ const ui32 ObjectId_ = FlatIds.Get();
+
+ static inline TAllValues AllValues_;
+ static inline thread_local TPerThreadValues Values_{&AllValues_};
+};
+
+
namespace NDetail {
template <typename T, size_t NumShards>