xref: /netbsd-src/external/gpl3/gdb/dist/gdbsupport/thread-pool.h (revision 32d1c65c71fbdb65a012e8392a62a757dd6853e9)
1 /* Thread pool
2 
3    Copyright (C) 2019-2024 Free Software Foundation, Inc.
4 
5    This file is part of GDB.
6 
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3 of the License, or
10    (at your option) any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
19 
20 #ifndef GDBSUPPORT_THREAD_POOL_H
21 #define GDBSUPPORT_THREAD_POOL_H
22 
23 #include <queue>
24 #include <vector>
25 #include <functional>
26 #include <chrono>
27 #if CXX_STD_THREAD
28 #include <thread>
29 #include <mutex>
30 #include <condition_variable>
31 #include <future>
32 #endif
33 #include <optional>
34 
35 namespace gdb
36 {
37 
38 #if CXX_STD_THREAD
39 
40 /* Simply use the standard future.  */
41 template<typename T>
42 using future = std::future<T>;
43 
44 /* ... and the standard future_status.  */
45 using future_status = std::future_status;
46 
47 #else /* CXX_STD_THREAD */
48 
49 /* A compatibility enum for std::future_status.  This is just the
50    subset needed by gdb.  */
51 enum class future_status
52 {
53   ready,
54   timeout,
55 };
56 
57 /* A compatibility wrapper for std::future.  Once <thread> and
58    <future> are available in all GCC builds -- should that ever happen
59    -- this can be removed.  GCC does not implement threading for
60    MinGW, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93687.
61 
62    Meanwhile, in this mode, there are no threads.  Tasks submitted to
63    the thread pool are invoked immediately and their result is stored
64    here.  The base template here simply wraps a T and provides some
65    std::future compatibility methods.  The provided methods are chosen
66    based on what GDB needs presently.  */
67 
68 template<typename T>
69 class future
70 {
71 public:
72 
73   explicit future (T value)
74     : m_value (std::move (value))
75   {
76   }
77 
78   future () = default;
79   future (future &&other) = default;
80   future (const future &other) = delete;
81   future &operator= (future &&other) = default;
82   future &operator= (const future &other) = delete;
83 
84   void wait () const { }
85 
86   template<class Rep, class Period>
87   future_status wait_for (const std::chrono::duration<Rep,Period> &duration)
88     const
89   {
90     return future_status::ready;
91   }
92 
93   T get () { return std::move (m_value); }
94 
95 private:
96 
97   T m_value;
98 };
99 
100 /* A specialization for void.  */
101 
102 template<>
103 class future<void>
104 {
105 public:
106   void wait () const { }
107 
108   template<class Rep, class Period>
109   future_status wait_for (const std::chrono::duration<Rep,Period> &duration)
110     const
111   {
112     return future_status::ready;
113   }
114 
115   void get () { }
116 };
117 
118 #endif /* CXX_STD_THREAD */
119 
120 
121 /* A thread pool.
122 
123    There is a single global thread pool, see g_thread_pool.  Tasks can
124    be submitted to the thread pool.  They will be processed in worker
125    threads as time allows.  */
126 class thread_pool
127 {
128 public:
129   /* The sole global thread pool.  */
130   static thread_pool *g_thread_pool;
131 
132   ~thread_pool ();
133   DISABLE_COPY_AND_ASSIGN (thread_pool);
134 
135   /* Set the thread count of this thread pool.  By default, no threads
136      are created -- the thread count must be set first.  */
137   void set_thread_count (size_t num_threads);
138 
139   /* Return the number of executing threads.  */
140   size_t thread_count () const
141   {
142 #if CXX_STD_THREAD
143     return m_thread_count;
144 #else
145     return 0;
146 #endif
147   }
148 
149   /* Post a task to the thread pool.  A future is returned, which can
150      be used to wait for the result.  */
151   future<void> post_task (std::function<void ()> &&func)
152   {
153 #if CXX_STD_THREAD
154     std::packaged_task<void ()> task (std::move (func));
155     future<void> result = task.get_future ();
156     do_post_task (std::packaged_task<void ()> (std::move (task)));
157     return result;
158 #else
159     func ();
160     return {};
161 #endif /* CXX_STD_THREAD */
162   }
163 
164   /* Post a task to the thread pool.  A future is returned, which can
165      be used to wait for the result.  */
166   template<typename T>
167   future<T> post_task (std::function<T ()> &&func)
168   {
169 #if CXX_STD_THREAD
170     std::packaged_task<T ()> task (std::move (func));
171     future<T> result = task.get_future ();
172     do_post_task (std::packaged_task<void ()> (std::move (task)));
173     return result;
174 #else
175     return future<T> (func ());
176 #endif /* CXX_STD_THREAD */
177   }
178 
179 private:
180 
181   thread_pool () = default;
182 
183 #if CXX_STD_THREAD
184   /* The callback for each worker thread.  */
185   void thread_function ();
186 
187   /* Post a task to the thread pool.  A future is returned, which can
188      be used to wait for the result.  */
189   void do_post_task (std::packaged_task<void ()> &&func);
190 
191   /* The current thread count.  */
192   size_t m_thread_count = 0;
193 
194   /* A convenience typedef for the type of a task.  */
195   typedef std::packaged_task<void ()> task_t;
196 
197   /* The tasks that have not been processed yet.  An optional is used
198      to represent a task.  If the optional is empty, then this means
199      that the receiving thread should terminate.  If the optional is
200      non-empty, then it is an actual task to evaluate.  */
201   std::queue<std::optional<task_t>> m_tasks;
202 
203   /* A condition variable and mutex that are used for communication
204      between the main thread and the worker threads.  */
205   std::condition_variable m_tasks_cv;
206   std::mutex m_tasks_mutex;
207   bool m_sized_at_least_once = false;
208 #endif /* CXX_STD_THREAD */
209 };
210 
211 }
212 
213 #endif /* GDBSUPPORT_THREAD_POOL_H */
214