VLink  2.1.0
A high-performance communication middleware
thread_pool.h
浏览该文件的文档.
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. A shutdown initiated by a worker detaches that worker instead of
102  * joining itself. The pool object itself is non-copyable.
103  */
105  public:
106  /**
107  * @brief Callback signature used for submitted tasks.
108  *
109  * @details
110  * Move-only (@c MoveFunction<void()>); see @c MessageLoop::Callback for rationale.
111  */
112  using Callback = MoveFunction<void()>;
113 
114  /**
115  * @enum Type
116  * @brief Queue implementation backing the pool.
117  */
118  enum Type : uint8_t {
119  kNormalType = 0, ///< Default mutex-protected FIFO queue.
120  kLockfreeType = 1, ///< Lock-free MPMC queue.
121  };
122 
123  /**
124  * @enum Strategy
125  * @brief Submission-side strategy applied when the bounded queue is full.
126  *
127  * @details
128  * Worker wake-up is independent of this enum. It controls only how @c post_task and
129  * @c invoke_task react when capacity is reached.
130  */
131  enum Strategy : uint8_t {
132  kOptimizationStrategy = 0, ///< Retry up to 10 times with 1 ms sleep; then drop one eligible task and push.
133  kPopStrategy = 1, ///< Immediately drop one eligible task and push the new one.
134  kBlockStrategy = 2, ///< Retry indefinitely with 1 ms sleep until capacity frees up.
135  };
136 
137  /**
138  * @brief Constructs a pool with @p thread_count workers and the default @c kNormalType queue.
139  *
140  * @param thread_count Worker thread count. Default: @c 4. Zero leaves the pool in
141  * the shutdown state.
142  */
143  explicit ThreadPool(size_t thread_count = 4U);
144 
145  /**
146  * @brief Constructs a pool with a custom queue implementation.
147  *
148  * @param thread_count Worker thread count. Zero leaves the pool in the shutdown state.
149  * @param type Queue implementation type.
150  */
151  explicit ThreadPool(size_t thread_count, Type type);
152 
153  /**
154  * @brief Destructor. Calls @c shutdown(); a current worker is detached instead of self-joined.
155  */
156  virtual ~ThreadPool();
157 
158  /**
159  * @brief Assigns a human-readable name to the pool and its workers (used by debuggers).
160  *
161  * @param name Display name.
162  */
163  void set_name(const std::string& name);
164 
165  /**
166  * @brief Returns the display name assigned via @c set_name().
167  *
168  * @return Reference to the stored name string.
169  */
170  [[nodiscard]] const std::string& get_name() const;
171 
172  /**
173  * @brief Returns the queue implementation used by this pool.
174  *
175  * @return Queue type.
176  */
177  [[nodiscard]] Type get_type() const;
178 
179  /**
180  * @brief Returns the current submission-side back-pressure strategy.
181  *
182  * @return Current strategy.
183  */
184  [[nodiscard]] Strategy get_strategy() const;
185 
186  /**
187  * @brief Updates the submission-side back-pressure strategy.
188  *
189  * @param strategy New strategy.
190  */
191  void set_strategy(Strategy strategy);
192 
193  /**
194  * @brief Marks the pool as quitting, drains in-flight tasks, and joins workers.
195  *
196  * @details
197  * After @c shutdown() returns, further submissions are rejected. Workers complete the
198  * task they are currently running plus any already-queued tasks before exiting. When
199  * called from a worker thread the calling worker's handle is detached instead of
200  * joined because a thread cannot join itself; the @c Impl block is kept alive via
201  * @c std::shared_ptr so the detached worker continues to see a valid pool state until
202  * it returns.
203  *
204  * @return @c true on the first successful shutdown; @c false on subsequent calls.
205  */
206  bool shutdown();
207 
208  /**
209  * @brief Submits a task to the queue for execution by a worker thread.
210  *
211  * @details
212  * Thread-safe. Returns @c false when the pool is already shut down or when overflow
213  * handling cannot make room for the new task. Overflow behaviour depends on the
214  * configured @c Strategy:
215  *
216  * - @c kOptimizationStrategy: retry up to 10 times with a 1 ms sleep, then drop one
217  * eligible task and push the new one.
218  * - @c kPopStrategy: drop one eligible task immediately and push the new one.
219  * - @c kBlockStrategy: retry indefinitely with 1 ms sleep until space is available.
220  *
221  * @note Drop-policy semantics:
222  * - @c kNormalType respects @c TaskDropPolicy::kProtected; protected tasks are never
223  * selected as eviction victims, and if every queued task is protected the post
224  * fails and returns @c false.
225  * - @c kLockfreeType does not track per-task drop policy; overflow drop simply removes
226  * one queued task regardless of how it was submitted.
227  *
228  * @param callback Task to execute.
229  * @return @c true when the task was eventually enqueued.
230  */
231  bool post_task(Callback&& callback);
232 
233  /**
234  * @brief Submits a task that produces an observable @c TaskHandle.
235  *
236  * @details
237  * Tracked counterpart of @c post_task(). The returned handle allows callers to wait
238  * for completion, request cooperative cancellation, and observe whether the task was
239  * rejected or dropped before execution.
240  *
241  * @param callback Task to execute.
242  * @param options Optional overflow, drop, and cancellation policy.
243  * @return Handle observing the posted task; the handle remains valid even when the
244  * post is rejected so callers can inspect @c state().
245  */
246  [[nodiscard]] TaskHandle post_task_handle(Callback&& callback, const PostTaskOptions& options = {});
247 
248  /**
249  * @brief Returns the current number of tasks waiting in the queue.
250  *
251  * @return Pending task count.
252  */
253  [[nodiscard]] size_t get_task_count() const;
254 
255  /**
256  * @brief Reports whether the calling thread is one of this pool's workers.
257  *
258  * @return @c true when called from a worker.
259  */
260  [[nodiscard]] bool is_in_work_thread() const;
261 
262  /**
263  * @brief Returns the maximum queue capacity.
264  *
265  * @return Maximum number of tasks that may be queued at the same time.
266  */
267  [[nodiscard]] virtual size_t get_max_task_count() const;
268 
269  /**
270  * @brief Submits a callable to a worker thread and returns a @c std::future for the result.
271  *
272  * @details
273  * Thread-safe. The future is satisfied once the callable returns. When posting fails
274  * the future becomes ready with a @c broken_promise / @c future_error result.
275  *
276  * @warning Do not block on the returned future from a pool worker thread while all
277  * workers are busy; doing so deadlocks the pool.
278  *
279  * @tparam FunctionT Callable type.
280  * @tparam ArgsT Argument types forwarded to the callable.
281  * @tparam ResultT Deduced result type.
282  * @param function Callable to dispatch.
283  * @param args Arguments to forward.
284  * @return Future resolved with the callable's result.
285  */
286  template <class FunctionT, class... ArgsT, typename ResultT = std::invoke_result_t<FunctionT, ArgsT...>>
287  [[nodiscard]] std::future<ResultT> invoke_task(FunctionT&& function, ArgsT&&... args);
288 
289  private:
290  void init();
291 
292  bool push_task(Callback&& callback, bool droppable,
294  const TaskHandle* submit_handle = nullptr);
295 
296  bool drop_one_normal_task();
297 
298  bool drop_one_lockfree_task(bool keep_reserved = false);
299 
300  bool reserve_lockfree_task(bool* was_empty = nullptr);
301 
302  void release_lockfree_task();
303 
304  bool push_lockfree_task(Callback&& callback);
305 
306  struct Impl;
307  std::shared_ptr<Impl> impl_;
308 
310 };
311 
312 ////////////////////////////////////////////////////////////////
313 /// Details
314 ////////////////////////////////////////////////////////////////
315 
316 template <class FunctionT, class... ArgsT, typename ResultT>
317 inline std::future<ResultT> ThreadPool::invoke_task(FunctionT&& function, ArgsT&&... args) {
318  auto bound = [function = std::forward<FunctionT>(function),
319  args = std::make_tuple(std::forward<ArgsT>(args)...)]() mutable -> ResultT {
320  return std::apply(
321  [&function](auto&&... unpacked_args) -> ResultT {
322  return std::invoke(function, std::forward<decltype(unpacked_args)>(unpacked_args)...);
323  },
324  args);
325  };
326 
327  if constexpr (kIsSupportMoveFunction) {
328  std::packaged_task<ResultT()> task(std::move(bound));
329  auto res = task.get_future();
330 
331  if VUNLIKELY (!post_task([task = std::move(task)]() mutable { task(); })) {
332  // Destroying the unposted packaged_task makes the returned future ready with broken_promise.
333  }
334 
335  return res;
336  } else {
337  auto task = MemoryResource::make_shared<std::packaged_task<ResultT()>>(std::move(bound));
338  auto res = task->get_future();
339 
340  if VUNLIKELY (!post_task([task]() mutable { (*task)(); })) {
341  task.reset();
342  }
343 
344  return res;
345  }
346 }
347 
348 } // 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.