diff options
author | Christian Biesinger <cbiesinger@google.com> | 2019-10-01 18:38:35 -0500 |
---|---|---|
committer | Tom Tromey <tom@tromey.com> | 2019-11-26 14:02:57 -0700 |
commit | a0b57563b1317e0000a67a7bed4c1712403682f3 (patch) | |
tree | 3a04ba4f878ec9d8dbfb03fd7a5fd345d8033fc9 /gdb | |
parent | 3b3978bca2a204a772563c8e121e4a02be72e802 (diff) | |
download | gdb-a0b57563b1317e0000a67a7bed4c1712403682f3.zip gdb-a0b57563b1317e0000a67a7bed4c1712403682f3.tar.gz gdb-a0b57563b1317e0000a67a7bed4c1712403682f3.tar.bz2 |
Implement a thread pool
This adds a simple thread pool to gdb. In the end, this seemed
preferable to the approach taken in an earlier version of this series;
namely, starting threads in the parallel-foreach implementation. This
approach reduces the overhead of starting new threads, and also lets
the user control (in a subsequent patch) exactly how many worker
threads are running.
gdb/ChangeLog
2019-11-26 Christian Biesinger <cbiesinger@google.com>
Tom Tromey <tom@tromey.com>
* gdbsupport/thread-pool.h: New file.
* gdbsupport/thread-pool.c: New file.
* Makefile.in (COMMON_SFILES): Add thread-pool.c.
(HFILES_NO_SRCDIR): Add thread-pool.h.
Change-Id: I597bb642780cb9d578ca92373d2a638efb44fe52
Diffstat (limited to 'gdb')
-rw-r--r-- | gdb/ChangeLog | 8 | ||||
-rw-r--r-- | gdb/Makefile.in | 2 | ||||
-rw-r--r-- | gdb/gdbsupport/thread-pool.c | 128 | ||||
-rw-r--r-- | gdb/gdbsupport/thread-pool.h | 90 |
4 files changed, 228 insertions, 0 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 48e7ca1..7c4e2b8 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,11 @@ +2019-11-26 Christian Biesinger <cbiesinger@google.com> + Tom Tromey <tom@tromey.com> + + * gdbsupport/thread-pool.h: New file. + * gdbsupport/thread-pool.c: New file. + * Makefile.in (COMMON_SFILES): Add thread-pool.c. + (HFILES_NO_SRCDIR): Add thread-pool.h. + 2019-11-26 Tom Tromey <tom@tromey.com> * event-top.h (thread_local_segv_handler): Declare. diff --git a/gdb/Makefile.in b/gdb/Makefile.in index c487b10..b07b11e 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -995,6 +995,7 @@ COMMON_SFILES = \ gdbsupport/signals.c \ gdbsupport/signals-state-save-restore.c \ gdbsupport/tdesc.c \ + gdbsupport/thread-pool.c \ gdbsupport/xml-utils.c \ complaints.c \ completer.c \ @@ -1499,6 +1500,7 @@ HFILES_NO_SRCDIR = \ gdbsupport/signals-state-save-restore.h \ gdbsupport/symbol.h \ gdbsupport/tdesc.h \ + gdbsupport/thread-pool.h \ gdbsupport/version.h \ gdbsupport/x86-xstate.h \ gdbsupport/xml-utils.h \ diff --git a/gdb/gdbsupport/thread-pool.c b/gdb/gdbsupport/thread-pool.c new file mode 100644 index 0000000..8282ea3 --- /dev/null +++ b/gdb/gdbsupport/thread-pool.c @@ -0,0 +1,128 @@ +/* Thread pool + + Copyright (C) 2019 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "common-defs.h" + +#if CXX_STD_THREAD + +#include "gdbsupport/thread-pool.h" +#include "gdbsupport/alt-stack.h" +#include "gdbsupport/block-signals.h" +#include <algorithm> + +namespace gdb +{ + +/* The thread pool detach()s its threads, so that the threads will not + prevent the process from exiting. However, it was discovered that + if any detached threads were still waiting on a condition variable, + then the condition variable's destructor would wait for the threads + to exit -- defeating the purpose. + + Allocating the thread pool on the heap and simply "leaking" it + avoids this problem. +*/ +thread_pool *thread_pool::g_thread_pool = new thread_pool (); + +thread_pool::~thread_pool () +{ + /* Because this is a singleton, we don't need to clean up. The + threads are detached so that they won't prevent process exit. + And, cleaning up here would be actively harmful in at least one + case -- see the comment by the definition of g_thread_pool. */ +} + +void +thread_pool::set_thread_count (size_t num_threads) +{ + std::lock_guard<std::mutex> guard (m_tasks_mutex); + + /* If the new size is larger, start some new threads. */ + if (m_thread_count < num_threads) + { + /* Ensure that signals used by gdb are blocked in the new + threads. */ + block_signals blocker; + for (size_t i = m_thread_count; i < num_threads; ++i) + { + std::thread thread (&thread_pool::thread_function, this); + thread.detach (); + } + } + /* If the new size is smaller, terminate some existing threads. */ + if (num_threads < m_thread_count) + { + for (size_t i = num_threads; i < m_thread_count; ++i) + m_tasks.emplace (); + m_tasks_cv.notify_all (); + } + + m_thread_count = num_threads; +} + +std::future<void> +thread_pool::post_task (std::function<void ()> func) +{ + std::packaged_task<void ()> t (func); + std::future<void> f = t.get_future (); + + if (m_thread_count == 0) + { + /* Just execute it now. */ + t (); + } + else + { + std::lock_guard<std::mutex> guard (m_tasks_mutex); + m_tasks.emplace (std::move (t)); + m_tasks_cv.notify_one (); + } + return f; +} + +void +thread_pool::thread_function () +{ + /* Ensure that SIGSEGV is delivered to an alternate signal + stack. */ + gdb::alternate_signal_stack signal_stack; + + while (true) + { + optional<task> t; + + { + /* We want to hold the lock while examining the task list, but + not while invoking the task function. */ + std::unique_lock<std::mutex> guard (m_tasks_mutex); + while (m_tasks.empty ()) + m_tasks_cv.wait (guard); + t = std::move (m_tasks.front()); + m_tasks.pop (); + } + + if (!t.has_value ()) + break; + (*t) (); + } +} + +} + +#endif /* CXX_STD_THREAD */ diff --git a/gdb/gdbsupport/thread-pool.h b/gdb/gdbsupport/thread-pool.h new file mode 100644 index 0000000..e1fcb38 --- /dev/null +++ b/gdb/gdbsupport/thread-pool.h @@ -0,0 +1,90 @@ +/* Thread pool + + Copyright (C) 2019 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef GDBSUPPORT_THREAD_POOL_H +#define GDBSUPPORT_THREAD_POOL_H + +#include <queue> +#include <thread> +#include <vector> +#include <functional> +#include <mutex> +#include <condition_variable> +#include <future> +#include "gdbsupport/gdb_optional.h" + +namespace gdb +{ + +/* A thread pool. + + There is a single global thread pool, see g_thread_pool. Tasks can + be submitted to the thread pool. They will be processed in worker + threads as time allows. */ +class thread_pool +{ +public: + /* The sole global thread pool. */ + static thread_pool *g_thread_pool; + + ~thread_pool (); + DISABLE_COPY_AND_ASSIGN (thread_pool); + + /* Set the thread count of this thread pool. By default, no threads + are created -- the thread count must be set first. */ + void set_thread_count (size_t num_threads); + + /* Return the number of executing threads. */ + size_t thread_count () const + { + return m_thread_count; + } + + /* Post a task to the thread pool. A future is returned, which can + be used to wait for the result. */ + std::future<void> post_task (std::function<void ()> func); + +private: + + thread_pool () = default; + + /* The callback for each worker thread. */ + void thread_function (); + + /* The current thread count. */ + size_t m_thread_count = 0; + + /* A convenience typedef for the type of a task. */ + typedef std::packaged_task<void ()> task; + + /* The tasks that have not been processed yet. An optional is used + to represent a task. If the optional is empty, then this means + that the receiving thread should terminate. If the optional is + non-empty, then it is an actual task to evaluate. */ + std::queue<optional<task>> m_tasks; + + /* A condition variable and mutex that are used for communication + between the main thread and the worker threads. */ + std::condition_variable m_tasks_cv; + std::mutex m_tasks_mutex; +}; + +} + +#endif /* GDBSUPPORT_THREAD_POOL_H */ |