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  

80  

 
81 +
    /** Wait for all outstanding work to complete.
 
82 +

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

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

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

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

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

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

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

113  

83 -
        Signals all threads to exit. Threads will finish their
114 +
        Signals all threads to exit after finishing their current
84 -
        current work item before exiting. Does not wait for
115 +
        work item. Queued work that has not started is abandoned.
85 -
        threads to exit.
116 +
        Does not wait for 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.
86  
    */
122  
    */
87  
    void
123  
    void
88  
    stop() noexcept;
124  
    stop() noexcept;
89  

125  

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

127  

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

133  

 
134 +
//------------------------------------------------------------------------------
 
135 +

98  
/** An executor that submits work to a thread_pool.
136  
/** An executor that submits work to a thread_pool.
99  

137  

100  
    Executors are lightweight handles that can be copied and stored.
138  
    Executors are lightweight handles that can be copied and stored.
101  
    All copies refer to the same underlying thread pool.
139  
    All copies refer to the same underlying thread pool.
102  

140  

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

148  

111  
    thread_pool* pool_ = nullptr;
149  
    thread_pool* pool_ = nullptr;
112  

150  

113  
    explicit
151  
    explicit
114  
    executor_type(thread_pool& pool) noexcept
152  
    executor_type(thread_pool& pool) noexcept
115  
        : pool_(&pool)
153  
        : pool_(&pool)
116  
    {
154  
    {
117  
    }
155  
    }
118  

156  

119  
public:
157  
public:
120 -
    /** Construct a default null executor.
158 +
    /// Default construct a null executor.
121 -

 
122 -
        The resulting executor is not associated with any pool.
 
123 -
        `context()`, `dispatch()`, and `post()` require the
 
124 -
        executor to be associated with a pool before use.
 
125 -
    */
 
126  
    executor_type() = default;
159  
    executor_type() = default;
127  

160  

128  
    /// Return the underlying thread pool.
161  
    /// Return the underlying thread pool.
129  
    thread_pool&
162  
    thread_pool&
130  
    context() const noexcept
163  
    context() const noexcept
131  
    {
164  
    {
132  
        return *pool_;
165  
        return *pool_;
133  
    }
166  
    }
134  

167  

135 -
    /// Notify that work has started (no-op for thread pools).
168 +
    /** Notify that work has started.
 
169 +

 
170 +
        Increments the outstanding work count. Must be paired
 
171 +
        with a subsequent call to @ref on_work_finished.
 
172 +

 
173 +
        @see on_work_finished, work_guard
 
174 +
    */
 
175 +
    BOOST_CAPY_DECL
136  
    void
176  
    void
137 -
    on_work_started() const noexcept
177 +
    on_work_started() const noexcept;
138 -
    {
 
139 -
    }
 
140  

178  

141 -
    /// Notify that work has finished (no-op for thread pools).
179 +
    /** Notify that work has finished.
 
180 +

 
181 +
        Decrements the outstanding work count. When the count
 
182 +
        reaches zero after @ref thread_pool::join has been called,
 
183 +
        the pool's worker threads are signaled to stop.
 
184 +

 
185 +
        @pre A preceding call to @ref on_work_started was made.
 
186 +

 
187 +
        @see on_work_started, work_guard
 
188 +
    */
 
189 +
    BOOST_CAPY_DECL
142  
    void
190  
    void
143 -
    on_work_finished() const noexcept
191 +
    on_work_finished() const noexcept;
144 -
    {
 
145 -
    }
 
146  

192  

147  
    /** Dispatch a coroutine for execution.
193  
    /** Dispatch a coroutine for execution.
148  

194  

149  
        Posts the coroutine to the thread pool for execution on a
195  
        Posts the coroutine to the thread pool for execution on a
150  
        worker thread and returns `std::noop_coroutine()`. Thread
196  
        worker thread and returns `std::noop_coroutine()`. Thread
151  
        pools never execute inline because no single thread "owns"
197  
        pools never execute inline because no single thread "owns"
152  
        the pool.
198  
        the pool.
153  

199  

154  
        @param h The coroutine handle to execute.
200  
        @param h The coroutine handle to execute.
155  

201  

156  
        @return `std::noop_coroutine()` always.
202  
        @return `std::noop_coroutine()` always.
157  
    */
203  
    */
158  
    std::coroutine_handle<>
204  
    std::coroutine_handle<>
159  
    dispatch(std::coroutine_handle<> h) const
205  
    dispatch(std::coroutine_handle<> h) const
160  
    {
206  
    {
161  
        post(h);
207  
        post(h);
162  
        return std::noop_coroutine();
208  
        return std::noop_coroutine();
163  
    }
209  
    }
164  

210  

165  
    /** Post a coroutine to the thread pool.
211  
    /** Post a coroutine to the thread pool.
166  

212  

167  
        The coroutine will be resumed on one of the pool's
213  
        The coroutine will be resumed on one of the pool's
168  
        worker threads.
214  
        worker threads.
169  

215  

170  
        @param h The coroutine handle to execute.
216  
        @param h The coroutine handle to execute.
171  
    */
217  
    */
172  
    BOOST_CAPY_DECL
218  
    BOOST_CAPY_DECL
173  
    void
219  
    void
174  
    post(std::coroutine_handle<> h) const;
220  
    post(std::coroutine_handle<> h) const;
175  

221  

176  
    /// Return true if two executors refer to the same thread pool.
222  
    /// Return true if two executors refer to the same thread pool.
177  
    bool
223  
    bool
178  
    operator==(executor_type const& other) const noexcept
224  
    operator==(executor_type const& other) const noexcept
179  
    {
225  
    {
180  
        return pool_ == other.pool_;
226  
        return pool_ == other.pool_;
181  
    }
227  
    }
182  
};
228  
};
183  

229  

184  
} // capy
230  
} // capy
185  
} // boost
231  
} // boost
186  

232  

187  
#endif
233  
#endif