1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
// Copyright (c) 2026 Michael Vandeberg
3  
// Copyright (c) 2026 Michael Vandeberg
4  
//
4  
//
5  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
6  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  
//
7  
//
8  
// Official repository: https://github.com/boostorg/capy
8  
// Official repository: https://github.com/boostorg/capy
9  
//
9  
//
10  

10  

11  
#ifndef BOOST_CAPY_EX_THREAD_POOL_HPP
11  
#ifndef BOOST_CAPY_EX_THREAD_POOL_HPP
12  
#define BOOST_CAPY_EX_THREAD_POOL_HPP
12  
#define BOOST_CAPY_EX_THREAD_POOL_HPP
13  

13  

14  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/detail/config.hpp>
15  
#include <coroutine>
15  
#include <coroutine>
16  
#include <boost/capy/ex/execution_context.hpp>
16  
#include <boost/capy/ex/execution_context.hpp>
17  
#include <cstddef>
17  
#include <cstddef>
18  
#include <string_view>
18  
#include <string_view>
19  

19  

20  
namespace boost {
20  
namespace boost {
21  
namespace capy {
21  
namespace capy {
22  

22  

23  
/** A pool of threads for executing work concurrently.
23  
/** A pool of threads for executing work concurrently.
24  

24  

25  
    Use this when you need to run coroutines on multiple threads
25  
    Use this when you need to run coroutines on multiple threads
26  
    without the overhead of creating and destroying threads for
26  
    without the overhead of creating and destroying threads for
27  
    each task. Work items are distributed across the pool using
27  
    each task. Work items are distributed across the pool using
28  
    a shared queue.
28  
    a shared queue.
29  

29  

30  
    @par Thread Safety
30  
    @par Thread Safety
31  
    Distinct objects: Safe.
31  
    Distinct objects: Safe.
32  
    Shared objects: Unsafe.
32  
    Shared objects: Unsafe.
33  

33  

34  
    @par Example
34  
    @par Example
35  
    @code
35  
    @code
36  
    thread_pool pool(4);  // 4 worker threads
36  
    thread_pool pool(4);  // 4 worker threads
37  
    auto ex = pool.get_executor();
37  
    auto ex = pool.get_executor();
38  
    ex.post(some_coroutine);
38  
    ex.post(some_coroutine);
39  
    // pool destructor waits for all work to complete
39  
    // pool destructor waits for all work to complete
40  
    @endcode
40  
    @endcode
41  
*/
41  
*/
42  
class BOOST_CAPY_DECL
42  
class BOOST_CAPY_DECL
43  
    thread_pool
43  
    thread_pool
44  
    : public execution_context
44  
    : public execution_context
45  
{
45  
{
46  
    class impl;
46  
    class impl;
47  
    impl* impl_;
47  
    impl* impl_;
48  

48  

49  
public:
49  
public:
50  
    class executor_type;
50  
    class executor_type;
51  

51  

52  
    /** Destroy the thread pool.
52  
    /** Destroy the thread pool.
53  

53  

54  
        Signals all worker threads to stop, waits for them to
54  
        Signals all worker threads to stop, waits for them to
55  
        finish, and destroys any pending work items.
55  
        finish, and destroys any pending work items.
56  
    */
56  
    */
57  
    ~thread_pool();
57  
    ~thread_pool();
58  

58  

59  
    /** Construct a thread pool.
59  
    /** Construct a thread pool.
60  

60  

61  
        Creates a pool with the specified number of worker threads.
61  
        Creates a pool with the specified number of worker threads.
62  
        If `num_threads` is zero, the number of threads is set to
62  
        If `num_threads` is zero, the number of threads is set to
63  
        the hardware concurrency, or one if that cannot be determined.
63  
        the hardware concurrency, or one if that cannot be determined.
64  

64  

65  
        @param num_threads The number of worker threads, or zero
65  
        @param num_threads The number of worker threads, or zero
66  
            for automatic selection.
66  
            for automatic selection.
67  

67  

68  
        @param thread_name_prefix The prefix for worker thread names.
68  
        @param thread_name_prefix The prefix for worker thread names.
69  
            Thread names appear as "{prefix}0", "{prefix}1", etc.
69  
            Thread names appear as "{prefix}0", "{prefix}1", etc.
70  
            The prefix is truncated to 12 characters. Defaults to
70  
            The prefix is truncated to 12 characters. Defaults to
71  
            "capy-pool-".
71  
            "capy-pool-".
72  
    */
72  
    */
73  
    explicit
73  
    explicit
74  
    thread_pool(
74  
    thread_pool(
75  
        std::size_t num_threads = 0,
75  
        std::size_t num_threads = 0,
76  
        std::string_view thread_name_prefix = "capy-pool-");
76  
        std::string_view thread_name_prefix = "capy-pool-");
77  

77  

78  
    thread_pool(thread_pool const&) = delete;
78  
    thread_pool(thread_pool const&) = delete;
79  
    thread_pool& operator=(thread_pool const&) = delete;
79  
    thread_pool& operator=(thread_pool const&) = delete;
80 -
    /** Wait for all outstanding work to complete.
 
81 -

 
82 -
        Releases the internal work guard, then blocks the calling
 
83 -
        thread until all outstanding work tracked by
 
84 -
        @ref executor_type::on_work_started and
 
85 -
        @ref executor_type::on_work_finished completes. After all
 
86 -
        work finishes, joins the worker threads.
 
87 -

 
88 -
        If @ref stop is called while `join()` is blocking, the
 
89 -
        pool stops without waiting for remaining work to
 
90 -
        complete. Worker threads finish their current item and
 
91 -
        exit; `join()` still waits for all threads to be joined
 
92 -
        before returning.
 
93 -

 
94 -
        This function is idempotent. The first call performs the
 
95 -
        join; subsequent calls return immediately.
 
96 -

 
97 -
        @par Preconditions
 
98 -
        Must not be called from a thread in this pool (undefined
 
99 -
        behavior).
 
100 -

 
101 -
        @par Postconditions
 
102 -
        All worker threads have been joined. The pool cannot be
 
103 -
        reused.
 
104 -

 
105 -
        @par Thread Safety
 
106 -
        May be called from any thread not in this pool.
 
107 -
    */
 
108 -
    void
 
109 -
    join() noexcept;
 
110 -

 
111  

80  

112  
    /** Request all worker threads to stop.
81  
    /** Request all worker threads to stop.
113  

82  

114 -
        Signals all threads to exit after finishing their current
83 +
        Signals all threads to exit. Threads will finish their
115 -
        work item. Queued work that has not started is abandoned.
84 +
        current work item before exiting. Does not wait for
116 -
        Does not wait for threads to exit.
85 +
        threads to exit.
117 -

 
118 -
        If @ref join is blocking on another thread, calling
 
119 -
        `stop()` causes it to stop waiting for outstanding
 
120 -
        work. The `join()` call still waits for worker threads
 
121 -
        to finish their current item and exit before returning.
 
122  
    */
86  
    */
123  
    void
87  
    void
124  
    stop() noexcept;
88  
    stop() noexcept;
125  

89  

126  
    /** Return an executor for this thread pool.
90  
    /** Return an executor for this thread pool.
127  

91  

128  
        @return An executor associated with this thread pool.
92  
        @return An executor associated with this thread pool.
129  
    */
93  
    */
130  
    executor_type
94  
    executor_type
131  
    get_executor() const noexcept;
95  
    get_executor() const noexcept;
132  
};
96  
};
133  

97  

 
98 +
//------------------------------------------------------------------------------
 
99 +

134  
/** An executor that submits work to a thread_pool.
100  
/** An executor that submits work to a thread_pool.
135  

101  

136  
    Executors are lightweight handles that can be copied and stored.
102  
    Executors are lightweight handles that can be copied and stored.
137  
    All copies refer to the same underlying thread pool.
103  
    All copies refer to the same underlying thread pool.
138  

104  

139  
    @par Thread Safety
105  
    @par Thread Safety
140  
    Distinct objects: Safe.
106  
    Distinct objects: Safe.
141  
    Shared objects: Safe.
107  
    Shared objects: Safe.
142  
*/
108  
*/
143  
class thread_pool::executor_type
109  
class thread_pool::executor_type
144  
{
110  
{
145  
    friend class thread_pool;
111  
    friend class thread_pool;
146  

112  

147  
    thread_pool* pool_ = nullptr;
113  
    thread_pool* pool_ = nullptr;
148  

114  

149  
    explicit
115  
    explicit
150  
    executor_type(thread_pool& pool) noexcept
116  
    executor_type(thread_pool& pool) noexcept
151  
        : pool_(&pool)
117  
        : pool_(&pool)
152  
    {
118  
    {
153  
    }
119  
    }
154  

120  

155  
public:
121  
public:
156 -
    /** Construct a default null executor.
122 +
    /// Default construct a null executor.
157 -

 
158 -
        The resulting executor is not associated with any pool.
 
159 -
        `context()`, `dispatch()`, and `post()` require the
 
160 -
        executor to be associated with a pool before use.
 
161 -
    */
 
162  
    executor_type() = default;
123  
    executor_type() = default;
163  

124  

164  
    /// Return the underlying thread pool.
125  
    /// Return the underlying thread pool.
165  
    thread_pool&
126  
    thread_pool&
166  
    context() const noexcept
127  
    context() const noexcept
167  
    {
128  
    {
168  
        return *pool_;
129  
        return *pool_;
169  
    }
130  
    }
170  

131  

171 -
    /** Notify that work has started.
132 +
    /// Notify that work has started (no-op for thread pools).
172 -

 
173 -
        Increments the outstanding work count. Must be paired
 
174 -
        with a subsequent call to @ref on_work_finished.
 
175 -

 
176 -
        @see on_work_finished, work_guard
 
177 -
    */
 
178 -
    BOOST_CAPY_DECL
 
179  
    void
133  
    void
180 -
    on_work_started() const noexcept;
134 +
    on_work_started() const noexcept
181 -

135 +
    {
182 -
    /** Notify that work has finished.
136 +
    }
183 -

 
184 -
        Decrements the outstanding work count. When the count
 
185 -
        reaches zero after @ref thread_pool::join has been called,
 
186 -
        the pool's worker threads are signaled to stop.
 
187 -

 
188 -
        @pre A preceding call to @ref on_work_started was made.
 
189  

137  

190 -
        @see on_work_started, work_guard
138 +
    /// Notify that work has finished (no-op for thread pools).
191 -
    */
 
192 -
    BOOST_CAPY_DECL
 
193  
    void
139  
    void
194 -
    on_work_finished() const noexcept;
140 +
    on_work_finished() const noexcept
 
141 +
    {
 
142 +
    }
195  

143  

196  
    /** Dispatch a coroutine for execution.
144  
    /** Dispatch a coroutine for execution.
197  

145  

198  
        Posts the coroutine to the thread pool for execution on a
146  
        Posts the coroutine to the thread pool for execution on a
199  
        worker thread and returns `std::noop_coroutine()`. Thread
147  
        worker thread and returns `std::noop_coroutine()`. Thread
200  
        pools never execute inline because no single thread "owns"
148  
        pools never execute inline because no single thread "owns"
201  
        the pool.
149  
        the pool.
202  

150  

203  
        @param h The coroutine handle to execute.
151  
        @param h The coroutine handle to execute.
204  

152  

205  
        @return `std::noop_coroutine()` always.
153  
        @return `std::noop_coroutine()` always.
206  
    */
154  
    */
207  
    std::coroutine_handle<>
155  
    std::coroutine_handle<>
208  
    dispatch(std::coroutine_handle<> h) const
156  
    dispatch(std::coroutine_handle<> h) const
209  
    {
157  
    {
210  
        post(h);
158  
        post(h);
211  
        return std::noop_coroutine();
159  
        return std::noop_coroutine();
212  
    }
160  
    }
213  

161  

214  
    /** Post a coroutine to the thread pool.
162  
    /** Post a coroutine to the thread pool.
215  

163  

216  
        The coroutine will be resumed on one of the pool's
164  
        The coroutine will be resumed on one of the pool's
217  
        worker threads.
165  
        worker threads.
218  

166  

219  
        @param h The coroutine handle to execute.
167  
        @param h The coroutine handle to execute.
220  
    */
168  
    */
221  
    BOOST_CAPY_DECL
169  
    BOOST_CAPY_DECL
222  
    void
170  
    void
223  
    post(std::coroutine_handle<> h) const;
171  
    post(std::coroutine_handle<> h) const;
224  

172  

225  
    /// Return true if two executors refer to the same thread pool.
173  
    /// Return true if two executors refer to the same thread pool.
226  
    bool
174  
    bool
227  
    operator==(executor_type const& other) const noexcept
175  
    operator==(executor_type const& other) const noexcept
228  
    {
176  
    {
229  
        return pool_ == other.pool_;
177  
        return pool_ == other.pool_;
230  
    }
178  
    }
231  
};
179  
};
232  

180  

233  
} // capy
181  
} // capy
234  
} // boost
182  
} // boost
235  

183  

236  
#endif
184  
#endif