VLink  2.0.0
A high-performance communication middleware
thread_pool.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file thread_pool.h
26  * @brief Fixed-size worker pool for parallel task execution.
27  *
28  * @details
29  * @c vlink::ThreadPool launches a configurable number of worker threads at construction
30  * and dispatches submitted tasks to whichever worker is idle. Unlike @c MessageLoop, the
31  * pool has no built-in timer mechanism or loop lifecycle; it is started immediately and
32  * torn down with @c shutdown(). A pool created with zero workers is left in the
33  * shutdown state and rejects every submission.
34  *
35  * Architecture:
36  *
37  * @verbatim
38  * post_task() -----> +---------------------------+
39  * | shared dispatcher queue |
40  * +-----+----------+----------+
41  * | | |
42  * v v v
43  * worker 1 worker 2 ... worker N
44  * | | |
45  * v v v
46  * user task user task user task
47  * @endverbatim
48  *
49  * Queue implementations:
50  *
51  * | Type | Backing queue | Notes |
52  * | ----------------- | ------------------------------------------------ | ------------------------------- |
53  * | @c kNormalType | Mutex-protected @c std::deque (or @c std::pmr) | Default; honours drop policy |
54  * | @c kLockfreeType | @c MpmcQueue (lock-free multi-producer/consumer) | Lower contention overhead |
55  *
56  * Push-side back-pressure strategies are identical to @c MessageLoop::Strategy. They
57  * only affect submission when the queue is full; worker wake-up is always driven by a
58  * condition variable.
59  *
60  * @note
61  * - Tasks may run concurrently; protect shared state externally.
62  * - @c invoke_task() returns a @c std::future; blocking on it from a pool worker can
63  * deadlock if every worker is busy.
64  * - @c is_in_work_thread() can detect re-entrant submissions.
65  *
66  * @par Example
67  * @code
68  * vlink::ThreadPool pool(8);
69  * pool.post_task([] { heavy_work(); });
70  *
71  * auto fut = pool.invoke_task([]() -> int { return compute(); });
72  * int result = fut.get();
73  *
74  * pool.shutdown();
75  * @endcode
76  */
77 
78 #pragma once
79 
80 #include <functional>
81 #include <future>
82 #include <memory>
83 #include <string>
84 #include <tuple>
85 #include <type_traits>
86 #include <utility>
87 
88 #include "./functional.h"
89 #include "./macros.h"
90 #include "./memory_resource.h"
91 #include "./task_handle.h"
92 
93 namespace vlink {
94 
95 /**
96  * @class ThreadPool
97  * @brief Fixed worker pool that consumes a shared task queue.
98  *
99  * @details
100  * Worker threads are created during construction and joined during @c shutdown() or
101  * destruction. The pool object itself is non-copyable.
102  */
104  public:
105  /**
106  * @brief Callback signature used for submitted tasks.
107  *
108  * @details
109  * Move-only (@c MoveFunction<void()>); see @c MessageLoop::Callback for rationale.
110  */
111  using Callback = MoveFunction<void()>;
112 
113  /**
114  * @enum Type
115  * @brief Queue implementation backing the pool.
116  */
117  enum Type : uint8_t {
118  kNormalType = 0, ///< Default mutex-protected FIFO queue.
119  kLockfreeType = 1, ///< Lock-free MPMC queue.
120  };
121 
122  /**
123  * @enum Strategy
124  * @brief Submission-side strategy applied when the bounded queue is full.
125  *
126  * @details
127  * Worker wake-up is independent of this enum. It controls only how @c post_task and
128  * @c invoke_task react when capacity is reached.
129  */
130  enum Strategy : uint8_t {
131  kOptimizationStrategy = 0, ///< Retry up to 10 times with 1 ms sleep; then drop one eligible task and push.
132  kPopStrategy = 1, ///< Immediately drop one eligible task and push the new one.
133  kBlockStrategy = 2, ///< Retry indefinitely with 1 ms sleep until capacity frees up.
134  };
135 
136  /**
137  * @brief Constructs a pool with @p thread_count workers and the default @c kNormalType queue.
138  *
139  * @param thread_count Worker thread count. Default: @c 4. Zero leaves the pool in
140  * the shutdown state.
141  */
142  explicit ThreadPool(size_t thread_count = 4U);
143 
144  /**
145  * @brief Constructs a pool with a custom queue implementation.
146  *
147  * @param thread_count Worker thread count. Zero leaves the pool in the shutdown state.
148  * @param type Queue implementation type.
149  */
150  explicit ThreadPool(size_t thread_count, Type type);
151 
152  /**
153  * @brief Destructor. Calls @c shutdown() and joins worker threads.
154  */
155  virtual ~ThreadPool();
156 
157  /**
158  * @brief Assigns a human-readable name to the pool and its workers (used by debuggers).
159  *
160  * @param name Display name.
161  */
162  void set_name(const std::string& name);
163 
164  /**
165  * @brief Returns the display name assigned via @c set_name().
166  *
167  * @return Reference to the stored name string.
168  */
169  [[nodiscard]] const std::string& get_name() const;
170 
171  /**
172  * @brief Returns the queue implementation used by this pool.
173  *
174  * @return Queue type.
175  */
176  [[nodiscard]] Type get_type() const;
177 
178  /**
179  * @brief Returns the current submission-side back-pressure strategy.
180  *
181  * @return Current strategy.
182  */
183  [[nodiscard]] Strategy get_strategy() const;
184 
185  /**
186  * @brief Updates the submission-side back-pressure strategy.
187  *
188  * @param strategy New strategy.
189  */
190  void set_strategy(Strategy strategy);
191 
192  /**
193  * @brief Marks the pool as quitting, drains in-flight tasks, and joins workers.
194  *
195  * @details
196  * After @c shutdown() returns, further submissions are rejected. Workers complete the
197  * task they are currently running plus any already-queued tasks before exiting. When
198  * called from a worker thread the calling worker's handle is detached instead of
199  * joined because a thread cannot join itself; the @c Impl block is kept alive via
200  * @c std::shared_ptr so the detached worker continues to see a valid pool state until
201  * it returns.
202  *
203  * @return @c true on the first successful shutdown; @c false on subsequent calls.
204  */
205  bool shutdown();
206 
207  /**
208  * @brief Submits a task to the queue for execution by a worker thread.
209  *
210  * @details
211  * Thread-safe. Returns @c false when the pool is already shut down or when overflow
212  * handling cannot make room for the new task. Overflow behaviour depends on the
213  * configured @c Strategy:
214  *
215  * - @c kOptimizationStrategy: retry up to 10 times with a 1 ms sleep, then drop one
216  * eligible task and push the new one.
217  * - @c kPopStrategy: drop one eligible task immediately and push the new one.
218  * - @c kBlockStrategy: retry indefinitely with 1 ms sleep until space is available.
219  *
220  * @note Drop-policy semantics:
221  * - @c kNormalType respects @c TaskDropPolicy::kProtected; protected tasks are never
222  * selected as eviction victims, and if every queued task is protected the post
223  * fails and returns @c false.
224  * - @c kLockfreeType does not track per-task drop policy; overflow drop simply removes
225  * one queued task regardless of how it was submitted.
226  *
227  * @param callback Task to execute.
228  * @return @c true when the task was eventually enqueued.
229  */
230  bool post_task(Callback&& callback);
231 
232  /**
233  * @brief Submits a task that produces an observable @c TaskHandle.
234  *
235  * @details
236  * Tracked counterpart of @c post_task(). The returned handle allows callers to wait
237  * for completion, request cooperative cancellation, and observe whether the task was
238  * rejected or dropped before execution.
239  *
240  * @param callback Task to execute.
241  * @param options Optional overflow, drop, and cancellation policy.
242  * @return Handle observing the posted task; the handle remains valid even when the
243  * post is rejected so callers can inspect @c state().
244  */
245  [[nodiscard]] TaskHandle post_task_handle(Callback&& callback, const PostTaskOptions& options = {});
246 
247  /**
248  * @brief Returns the current number of tasks waiting in the queue.
249  *
250  * @return Pending task count.
251  */
252  [[nodiscard]] size_t get_task_count() const;
253 
254  /**
255  * @brief Reports whether the calling thread is one of this pool's workers.
256  *
257  * @return @c true when called from a worker.
258  */
259  [[nodiscard]] bool is_in_work_thread() const;
260 
261  /**
262  * @brief Returns the maximum queue capacity.
263  *
264  * @return Maximum number of tasks that may be queued at the same time.
265  */
266  [[nodiscard]] virtual size_t get_max_task_count() const;
267 
268  /**
269  * @brief Submits a callable to a worker thread and returns a @c std::future for the result.
270  *
271  * @details
272  * Thread-safe. The future is satisfied once the callable returns. When posting fails
273  * the future becomes ready with a @c broken_promise / @c future_error result.
274  *
275  * @warning Do not block on the returned future from a pool worker thread while all
276  * workers are busy; doing so deadlocks the pool.
277  *
278  * @tparam FunctionT Callable type.
279  * @tparam ArgsT Argument types forwarded to the callable.
280  * @tparam ResultT Deduced result type.
281  * @param function Callable to dispatch.
282  * @param args Arguments to forward.
283  * @return Future resolved with the callable's result.
284  */
285  template <class FunctionT, class... ArgsT, typename ResultT = std::invoke_result_t<FunctionT, ArgsT...>>
286  [[nodiscard]] std::future<ResultT> invoke_task(FunctionT&& function, ArgsT&&... args);
287 
288  private:
289  void init();
290 
291  bool push_task(Callback&& callback, bool droppable,
293  const TaskHandle* submit_handle = nullptr);
294 
295  bool drop_one_normal_task();
296 
297  bool drop_one_lockfree_task(bool keep_reserved = false);
298 
299  bool reserve_lockfree_task(bool* was_empty = nullptr);
300 
301  void release_lockfree_task();
302 
303  bool push_lockfree_task(Callback&& callback);
304 
305  struct Impl;
306  std::shared_ptr<Impl> impl_;
307 
309 };
310 
311 ////////////////////////////////////////////////////////////////
312 /// Details
313 ////////////////////////////////////////////////////////////////
314 
315 template <class FunctionT, class... ArgsT, typename ResultT>
316 inline std::future<ResultT> ThreadPool::invoke_task(FunctionT&& function, ArgsT&&... args) {
317  auto bound = [function = std::forward<FunctionT>(function),
318  args = std::make_tuple(std::forward<ArgsT>(args)...)]() mutable -> ResultT {
319  return std::apply(
320  [&function](auto&&... unpacked_args) -> ResultT {
321  return std::invoke(function, std::forward<decltype(unpacked_args)>(unpacked_args)...);
322  },
323  args);
324  };
325 
326  if constexpr (kIsSupportMoveFunction) {
327  std::packaged_task<ResultT()> task(std::move(bound));
328  auto res = task.get_future();
329 
330  if VUNLIKELY (!post_task([task = std::move(task)]() mutable { task(); })) {
331  // Destroying the unposted packaged_task makes the returned future ready with broken_promise.
332  }
333 
334  return res;
335  } else {
336  auto task = MemoryResource::make_shared<std::packaged_task<ResultT()>>(std::move(bound));
337  auto res = task->get_future();
338 
339  if VUNLIKELY (!post_task([task]() mutable { (*task)(); })) {
340  task.reset();
341  }
342 
343  return res;
344  }
345 }
346 
347 } // namespace vlink
Pool-backed type-erased callables: copyable vlink::Function and move-only vlink::MoveFunction.
Cross-platform macros for visibility, branch hints, copy prevention, singletons and string helpers.
#define VUNLIKELY(...)
Short alias for VLINK_UNLIKELY.
Definition: macros.h:289
#define VLINK_EXPORT
Definition: macros.h:81
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
Definition: macros.h:174
PMR adapter that lets standard pmr-aware containers allocate through vlink::MemoryPool.
Observable handle returned by tracked task-posting APIs of MessageLoop and ThreadPool.