dispenso 1.6.2
A library for task parallelism
Loading...
Searching...
No Matches
resource_pool.h
Go to the documentation of this file.
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
15#pragma once
16
17#include <dispenso/platform.h>
19#include <moodycamel/blockingconcurrentqueue.h>
20
21namespace dispenso {
22
23template <typename T>
24class ResourcePool;
25
30template <typename T>
31class Resource {
32 public:
34 Resource(Resource&& other) : resource_(other.resource_), pool_(other.pool_) {
35 other.resource_ = nullptr;
36 }
37
38 Resource& operator=(Resource&& other) {
39 if (&other != this) {
40 recycle();
41 resource_ = other.resource_;
42 pool_ = other.pool_;
43 other.resource_ = nullptr;
44 }
45 return *this;
46 }
47
53 T& get() {
54 return *resource_;
55 }
56
57 ~Resource() {
58 recycle();
59 }
60
61 private:
62 Resource(T* res, ResourcePool<T>* pool) : resource_(res), pool_(pool) {}
63
64 void recycle();
65
66 T* resource_;
67 ResourcePool<T>* pool_;
68
69 friend class ResourcePool<T>;
70};
71
76template <typename T>
78 public:
86 template <typename F>
87 ResourcePool(size_t size, const F& init)
88 : pool_(size),
89 backingResources_(reinterpret_cast<char*>(
90 detail::alignedMallocOrThrow(size * detail::alignToCacheLine(sizeof(T))))),
91 size_(size) {
92 char* buf = backingResources_;
93
94 // There are three reasons we create our own buffer and use placement new:
95 // 1. We want to be able to handle non-movable non-copyable objects
96 // * Note that we could do this with std::deque
97 // 2. We want to minimize memory allocations, since that can be a common point of contention in
98 // multithreaded programs.
99 // 3. We can easily ensure that the objects are cache aligned to help avoid false sharing.
100
101 // If init() or a T constructor throws, the destructor will not run, so release what has been
102 // built so far here.
103 struct Cleanup {
104 char* backing;
105 size_t constructed;
106 ~Cleanup() {
107 if (!backing) {
108 return;
109 }
110 for (size_t i = 0; i < constructed; ++i) {
111 reinterpret_cast<T*>(backing + i * detail::alignToCacheLine(sizeof(T)))->~T();
112 }
113 detail::alignedFree(backing);
114 }
115 } cleanup{backingResources_, 0};
116
117 while (cleanup.constructed < size) {
118 T* t = new (buf) T(init());
119 ++cleanup.constructed;
120 pool_.enqueue(t);
121 buf += detail::alignToCacheLine(sizeof(T));
122 }
123 cleanup.backing = nullptr;
124 }
125
132 T* t;
133 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_BEGIN();
134 pool_.wait_dequeue(t);
135 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_END();
136 return Resource<T>(t, this);
137 }
138
144 assert(pool_.size_approx() == size_);
145 for (size_t i = 0; i < size_; ++i) {
146 T* t;
147 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_BEGIN();
148 pool_.wait_dequeue(t);
149 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_END();
150 t->~T();
151 }
152 detail::alignedFree(backingResources_);
153 }
154
155 private:
156 void recycle(T* t) {
157 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_BEGIN();
158 pool_.enqueue(t);
159 DISPENSO_TSAN_ANNOTATE_IGNORE_WRITES_END();
160 }
161
162 moodycamel::BlockingConcurrentQueue<T*> pool_;
163 char* backingResources_;
164 size_t size_;
165
166 friend class Resource<T>;
167};
168
169template <typename T>
170void Resource<T>::recycle() {
171 if (resource_) {
172 pool_->recycle(resource_);
173 }
174}
175
176} // namespace dispenso
Resource(Resource &&other)
Resource< T > acquire()
ResourcePool(size_t size, const F &init)