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