1 /* Thread pool 2 3 Copyright (C) 2019-2023 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 #if CXX_STD_THREAD 27 #include <thread> 28 #include <mutex> 29 #include <condition_variable> 30 #include <future> 31 #endif 32 #include "gdbsupport/gdb_optional.h" 33 34 namespace gdb 35 { 36 37 #if CXX_STD_THREAD 38 39 /* Simply use the standard future. */ 40 template<typename T> 41 using future = std::future<T>; 42 43 #else /* CXX_STD_THREAD */ 44 45 /* A compatibility wrapper for std::future. Once <thread> and 46 <future> are available in all GCC builds -- should that ever happen 47 -- this can be removed. GCC does not implement threading for 48 MinGW, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93687. 49 50 Meanwhile, in this mode, there are no threads. Tasks submitted to 51 the thread pool are invoked immediately and their result is stored 52 here. The base template here simply wraps a T and provides some 53 std::future compatibility methods. The provided methods are chosen 54 based on what GDB needs presently. */ 55 56 template<typename T> 57 class future 58 { 59 public: 60 61 explicit future (T value) 62 : m_value (std::move (value)) 63 { 64 } 65 66 future () = default; 67 future (future &&other) = default; 68 future (const future &other) = delete; 69 future &operator= (future &&other) = default; 70 future &operator= (const future &other) = delete; 71 72 void wait () const { } 73 74 T get () { return std::move (m_value); } 75 76 private: 77 78 T m_value; 79 }; 80 81 /* A specialization for void. */ 82 83 template<> 84 class future<void> 85 { 86 public: 87 void wait () const { } 88 void get () { } 89 }; 90 91 #endif /* CXX_STD_THREAD */ 92 93 94 /* A thread pool. 95 96 There is a single global thread pool, see g_thread_pool. Tasks can 97 be submitted to the thread pool. They will be processed in worker 98 threads as time allows. */ 99 class thread_pool 100 { 101 public: 102 /* The sole global thread pool. */ 103 static thread_pool *g_thread_pool; 104 105 ~thread_pool (); 106 DISABLE_COPY_AND_ASSIGN (thread_pool); 107 108 /* Set the thread count of this thread pool. By default, no threads 109 are created -- the thread count must be set first. */ 110 void set_thread_count (size_t num_threads); 111 112 /* Return the number of executing threads. */ 113 size_t thread_count () const 114 { 115 #if CXX_STD_THREAD 116 return m_thread_count; 117 #else 118 return 0; 119 #endif 120 } 121 122 /* Post a task to the thread pool. A future is returned, which can 123 be used to wait for the result. */ 124 future<void> post_task (std::function<void ()> &&func) 125 { 126 #if CXX_STD_THREAD 127 std::packaged_task<void ()> task (std::move (func)); 128 future<void> result = task.get_future (); 129 do_post_task (std::packaged_task<void ()> (std::move (task))); 130 return result; 131 #else 132 func (); 133 return {}; 134 #endif /* CXX_STD_THREAD */ 135 } 136 137 /* Post a task to the thread pool. A future is returned, which can 138 be used to wait for the result. */ 139 template<typename T> 140 future<T> post_task (std::function<T ()> &&func) 141 { 142 #if CXX_STD_THREAD 143 std::packaged_task<T ()> task (std::move (func)); 144 future<T> result = task.get_future (); 145 do_post_task (std::packaged_task<void ()> (std::move (task))); 146 return result; 147 #else 148 return future<T> (func ()); 149 #endif /* CXX_STD_THREAD */ 150 } 151 152 private: 153 154 thread_pool () = default; 155 156 #if CXX_STD_THREAD 157 /* The callback for each worker thread. */ 158 void thread_function (); 159 160 /* Post a task to the thread pool. A future is returned, which can 161 be used to wait for the result. */ 162 void do_post_task (std::packaged_task<void ()> &&func); 163 164 /* The current thread count. */ 165 size_t m_thread_count = 0; 166 167 /* A convenience typedef for the type of a task. */ 168 typedef std::packaged_task<void ()> task_t; 169 170 /* The tasks that have not been processed yet. An optional is used 171 to represent a task. If the optional is empty, then this means 172 that the receiving thread should terminate. If the optional is 173 non-empty, then it is an actual task to evaluate. */ 174 std::queue<optional<task_t>> m_tasks; 175 176 /* A condition variable and mutex that are used for communication 177 between the main thread and the worker threads. */ 178 std::condition_variable m_tasks_cv; 179 std::mutex m_tasks_mutex; 180 #endif /* CXX_STD_THREAD */ 181 }; 182 183 } 184 185 #endif /* GDBSUPPORT_THREAD_POOL_H */ 186