ThreadSchedule 2.2.0
Modern C++ thread management library
Loading...
Searching...
No Matches
scheduled_pool.hpp
Go to the documentation of this file.
1#pragma once
2
7
8#include "expected.hpp"
9#include "thread_pool.hpp"
10#include <atomic>
11#include <chrono>
12#include <condition_variable>
13#include <future>
14#include <functional>
15#include <map>
16#include <memory>
17#include <mutex>
18#include <thread>
19
20namespace threadschedule
21{
22
36{
37 public:
38 explicit ScheduledTaskHandle(uint64_t id) : id_(id), cancelled_(std::make_shared<std::atomic<bool>>(false))
39 {
40 }
41
42 void cancel()
43 {
44 cancelled_->store(true, std::memory_order_release);
45 }
46
47 [[nodiscard]] auto is_cancelled() const noexcept -> bool
48 {
49 return cancelled_->load(std::memory_order_acquire);
50 }
51
52 [[nodiscard]] auto id() const noexcept -> uint64_t
53 {
54 return id_;
55 }
56
57 private:
58 uint64_t id_;
59 std::shared_ptr<std::atomic<bool>> cancelled_;
60
61 template <typename>
63 [[nodiscard]] auto get_cancel_flag() const -> std::shared_ptr<std::atomic<bool>>
64 {
65 return cancelled_;
66 }
67};
68
138template <typename PoolType = ThreadPool>
140{
141 public:
142 using Task = std::function<void()>;
145 using TimePoint = std::chrono::steady_clock::time_point;
146 using Duration = std::chrono::steady_clock::duration;
147
149 {
150 uint64_t id;
152 Duration interval; // Zero for one-time tasks
154 std::shared_ptr<PeriodicTask> periodic_task;
155 std::shared_ptr<std::atomic<bool>> cancelled;
157 };
158
163 explicit ScheduledThreadPoolT(size_t worker_threads = std::thread::hardware_concurrency())
164 : pool_(worker_threads), stop_(false), next_task_id_(1)
165 {
166 std::promise<Tid> scheduler_started;
167 auto scheduler_ready = scheduler_started.get_future();
168
169 scheduler_thread_ = ThreadWrapper([this, started = std::move(scheduler_started)]() mutable {
170 started.set_value(ThreadInfo::get_thread_id());
171 scheduler_loop();
172 });
173
174 scheduler_tid_ = scheduler_ready.get();
175 (void)ThreadInfo(scheduler_tid_).set_name("ts_sched_pool");
176 }
177
180
182 {
183 shutdown();
184 }
185
193 {
194 auto run_time = std::chrono::steady_clock::now() + delay;
195 return insert_one_shot_task(run_time, detail::make_move_callable<void()>(std::move(task)));
196 }
197
198 template <typename F, std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<F>, Task>, int> = 0>
200 {
201 auto run_time = std::chrono::steady_clock::now() + delay;
202 return insert_one_shot_task(run_time, detail::make_move_callable<void()>(std::forward<F>(task)));
203 }
204
212 {
213 return insert_one_shot_task(time_point, detail::make_move_callable<void()>(std::move(task)));
214 }
215
216 template <typename F, std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<F>, Task>, int> = 0>
217 auto schedule_at(TimePoint time_point, F&& task) -> ScheduledTaskHandle
218 {
219 return insert_one_shot_task(time_point, detail::make_move_callable<void()>(std::forward<F>(task)));
220 }
221
232 {
233 return schedule_periodic_after(Duration::zero(), interval, std::move(task));
234 }
235
236 template <typename F, std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<F>, Task>, int> = 0>
238 {
239 return schedule_periodic_after(Duration::zero(), interval, std::forward<F>(task));
240 }
241
249 auto schedule_periodic_after(Duration initial_delay, Duration interval, Task task) -> ScheduledTaskHandle
250 {
251 auto const run_time = std::chrono::steady_clock::now() + initial_delay;
252 return insert_periodic_task(run_time, interval, detail::make_copyable_callable<void()>(std::move(task)));
253 }
254
255 template <typename F, std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<F>, Task>, int> = 0>
256 auto schedule_periodic_after(Duration initial_delay, Duration interval, F&& task) -> ScheduledTaskHandle
257 {
258 auto const run_time = std::chrono::steady_clock::now() + initial_delay;
259 return insert_periodic_task(run_time, interval,
260 detail::make_copyable_callable<void()>(std::forward<F>(task)));
261 }
262
269 static void cancel(ScheduledTaskHandle& handle)
270 {
271 handle.cancel();
272 }
273
277 [[nodiscard]] auto scheduled_count() const -> size_t
278 {
279 std::lock_guard<std::mutex> lock(mutex_);
280 return scheduled_tasks_.size();
281 }
282
286 [[nodiscard]] auto thread_pool() -> PoolType&
287 {
288 return pool_;
289 }
290
294 void shutdown()
295 {
296 {
297 std::lock_guard<std::mutex> lock(mutex_);
298 if (stop_)
299 return;
300 stop_ = true;
301 }
302
303 condition_.notify_one();
304
305 if (scheduler_thread_.joinable())
306 {
307 scheduler_thread_.join();
308 }
309
310 pool_.shutdown();
311 }
312
318 auto configure_threads(std::string const& name_prefix, SchedulingPolicy policy = SchedulingPolicy::OTHER,
320 {
321 return pool_.configure_threads(name_prefix, policy, priority);
322 }
323
324 [[nodiscard]] auto scheduler_thread_info() const -> std::optional<ThreadInfo>
325 {
326 if (!scheduler_thread_.joinable() || scheduler_tid_ == Tid{})
327 return std::nullopt;
328 return ThreadInfo(scheduler_tid_);
329 }
330
334 {
335 auto info = scheduler_thread_info();
336 if (!info.has_value())
337 return unexpected(std::make_error_code(std::errc::no_such_process));
338 return detail::configure_thread(info.value(), name, policy, priority);
339 }
340
341 private:
342 PoolType pool_;
343 ThreadWrapper scheduler_thread_;
344 Tid scheduler_tid_{};
345
346 mutable std::mutex mutex_;
347 std::condition_variable condition_;
348 std::atomic<bool> stop_;
349
350 std::multimap<TimePoint, ScheduledTaskInfo> scheduled_tasks_;
351 std::atomic<uint64_t> next_task_id_;
352
353 auto insert_one_shot_task(TimePoint run_time, OneShotTask task) -> ScheduledTaskHandle
354 {
355 std::lock_guard<std::mutex> lock(mutex_);
356
357 uint64_t const task_id = next_task_id_++;
358 ScheduledTaskHandle handle(task_id);
359
360 if (stop_)
361 {
362 handle.cancel();
363 return handle;
364 }
365
367 info.id = task_id;
368 info.next_run = run_time;
369 info.interval = Duration::zero();
370 info.one_shot_task = std::move(task);
371 info.cancelled = handle.get_cancel_flag();
372 info.periodic = false;
373
374 scheduled_tasks_.insert({run_time, std::move(info)});
375 condition_.notify_one();
376
377 return handle;
378 }
379
380 auto insert_periodic_task(TimePoint run_time, Duration interval, PeriodicTask task) -> ScheduledTaskHandle
381 {
382 std::lock_guard<std::mutex> lock(mutex_);
383
384 uint64_t const task_id = next_task_id_++;
385 ScheduledTaskHandle handle(task_id);
386
387 if (stop_)
388 {
389 handle.cancel();
390 return handle;
391 }
392
394 info.id = task_id;
395 info.next_run = run_time;
396 info.interval = interval;
397 info.periodic_task = std::make_shared<PeriodicTask>(std::move(task));
398 info.cancelled = handle.get_cancel_flag();
399 info.periodic = true;
400
401 scheduled_tasks_.insert({run_time, std::move(info)});
402 condition_.notify_one();
403
404 return handle;
405 }
406
407 void scheduler_loop()
408 {
409 while (true)
410 {
411 std::unique_lock<std::mutex> lock(mutex_);
412
413 // Wait until we have tasks or need to stop
414 if (scheduled_tasks_.empty())
415 {
416 condition_.wait(lock, [this] { return stop_ || !scheduled_tasks_.empty(); });
417
418 if (stop_)
419 return;
420 }
421
422 // Get the next task to execute
423 auto const now = std::chrono::steady_clock::now();
424 auto it = scheduled_tasks_.begin();
425
426 if (it == scheduled_tasks_.end())
427 {
428 continue;
429 }
430
431 // Wait until it's time to execute
432 if (it->first > now)
433 {
434 condition_.wait_until(lock, it->first, [this] { return stop_.load(); });
435
436 if (stop_)
437 return;
438
439 continue;
440 }
441
442 // Extract the task info
443 ScheduledTaskInfo info = std::move(it->second);
444 scheduled_tasks_.erase(it);
445
446 // Check if cancelled
447 if (info.cancelled->load(std::memory_order_acquire))
448 {
449 continue;
450 }
451
452 // Schedule for execution in the thread pool
453 try
454 {
455 auto cancelled_flag = info.cancelled;
456 if (info.periodic)
457 {
458 auto periodic_task = info.periodic_task;
459 pool_.post([periodic_task, cancelled_flag]() {
460 if (!cancelled_flag->load(std::memory_order_acquire))
461 {
462 (*periodic_task)();
463 }
464 });
465
466 if (!info.cancelled->load(std::memory_order_acquire))
467 {
468 info.next_run += info.interval;
469 scheduled_tasks_.insert({info.next_run, std::move(info)});
470 }
471 }
472 else
473 {
474 auto one_shot_task = std::move(info.one_shot_task);
475 pool_.post([task = std::move(one_shot_task), cancelled_flag]() mutable {
476 if (!cancelled_flag->load(std::memory_order_acquire))
477 {
478 task();
479 }
480 });
481 }
482 }
483 catch (...)
484 {
485 // Thread pool might be shutting down
486 }
487 }
488 }
489};
490
499
500} // namespace threadschedule
Copyable handle for a cancellable scheduled task.
auto id() const noexcept -> uint64_t
auto is_cancelled() const noexcept -> bool
Thread pool augmented with delayed and periodic task scheduling.
auto schedule_at(TimePoint time_point, F &&task) -> ScheduledTaskHandle
auto scheduled_count() const -> size_t
Get number of scheduled tasks (including periodic)
void shutdown()
Shutdown the scheduler and wait for completion.
auto scheduler_thread_info() const -> std::optional< ThreadInfo >
auto schedule_periodic(Duration interval, F &&task) -> ScheduledTaskHandle
auto thread_pool() -> PoolType &
Get the underlying thread pool for direct task submission.
auto operator=(ScheduledThreadPoolT const &) -> ScheduledThreadPoolT &=delete
auto schedule_after(Duration delay, F &&task) -> ScheduledTaskHandle
ScheduledThreadPoolT(size_t worker_threads=std::thread::hardware_concurrency())
Create a scheduled thread pool.
auto schedule_periodic_after(Duration initial_delay, Duration interval, F &&task) -> ScheduledTaskHandle
static void cancel(ScheduledTaskHandle &handle)
Cancel a scheduled task by handle.
auto schedule_periodic_after(Duration initial_delay, Duration interval, Task task) -> ScheduledTaskHandle
Schedule a task to run periodically after an initial delay.
std::chrono::steady_clock::time_point TimePoint
std::chrono::steady_clock::duration Duration
auto configure_scheduler_thread(std::string const &name, SchedulingPolicy policy=SchedulingPolicy::OTHER, ThreadPriority priority=ThreadPriority::normal()) -> expected< void, std::error_code >
ScheduledThreadPoolT(ScheduledThreadPoolT const &)=delete
auto schedule_after(Duration delay, Task task) -> ScheduledTaskHandle
Schedule a task to run after a delay.
detail::copyable_callable< void()> PeriodicTask
auto configure_threads(std::string const &name_prefix, SchedulingPolicy policy=SchedulingPolicy::OTHER, ThreadPriority priority=ThreadPriority::normal())
Configure worker threads.
detail::move_callable< void()> OneShotTask
auto schedule_at(TimePoint time_point, Task task) -> ScheduledTaskHandle
Schedule a task to run at a specific time point.
auto schedule_periodic(Duration interval, Task task) -> ScheduledTaskHandle
Schedule a task to run periodically at fixed intervals.
Lightweight handle for querying and controlling a specific OS thread.
static auto get_thread_id() -> Tid
auto set_name(std::string const &name) const -> expected< void, std::error_code >
Value-semantic wrapper for a thread scheduling priority.
static constexpr auto normal() noexcept -> ThreadPriority
Owning wrapper around std::thread with RAII join-on-destroy semantics.
A result type that holds either a value of type T or an error of type E.
Definition expected.hpp:215
Exception thrown by expected::value() when the object is in the error state.
Definition expected.hpp:162
Polyfill for std::expected (C++23) for pre-C++23 compilers.
std::function< Signature > copyable_callable
Definition callable.hpp:40
auto make_move_callable(Callable &&callable) -> move_callable< Signature >
Definition callable.hpp:111
auto configure_thread(ThreadLike &thread, std::string const &name, SchedulingPolicy policy, ThreadPriority priority) -> expected< void, std::error_code >
std::function< Signature > move_callable
Definition callable.hpp:32
auto make_copyable_callable(Callable &&callable) -> copyable_callable< Signature >
Definition callable.hpp:117
SchedulingPolicy
Enumeration of available thread scheduling policies.
@ OTHER
Standard round-robin time-sharing.
ScheduledThreadPoolT< ThreadPool > ScheduledThreadPool
ScheduledThreadPoolT using the default ThreadPool backend.
ScheduledThreadPoolT< FastThreadPool > ScheduledFastThreadPool
ScheduledThreadPoolT using FastThreadPool as backend.
ScheduledThreadPoolT< LightweightPool > ScheduledLightweightPool
ScheduledThreadPoolT using LightweightPool as backend (minimal overhead).
ScheduledThreadPoolT< HighPerformancePool > ScheduledHighPerformancePool
ScheduledThreadPoolT using HighPerformancePool as backend.
std::shared_ptr< std::atomic< bool > > cancelled
Thread pools: HighPerformancePool, ThreadPoolBase, LightweightPoolT, and GlobalPool.