From a0b57563b1317e0000a67a7bed4c1712403682f3 Mon Sep 17 00:00:00 2001 From: Christian Biesinger Date: Tue, 1 Oct 2019 18:38:35 -0500 Subject: [PATCH] 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 Tom Tromey * 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 --- gdb/ChangeLog | 8 +++ gdb/Makefile.in | 2 + gdb/gdbsupport/thread-pool.c | 128 +++++++++++++++++++++++++++++++++++ gdb/gdbsupport/thread-pool.h | 90 ++++++++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 gdb/gdbsupport/thread-pool.c create mode 100644 gdb/gdbsupport/thread-pool.h diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 48e7ca1ca09..7c4e2b84582 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,11 @@ +2019-11-26 Christian Biesinger + Tom Tromey + + * 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 * event-top.h (thread_local_segv_handler): Declare. diff --git a/gdb/Makefile.in b/gdb/Makefile.in index c487b10af89..b07b11ec569 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 00000000000..8282ea374bc --- /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 . */ + +#include "common-defs.h" + +#if CXX_STD_THREAD + +#include "gdbsupport/thread-pool.h" +#include "gdbsupport/alt-stack.h" +#include "gdbsupport/block-signals.h" +#include + +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 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 +thread_pool::post_task (std::function func) +{ + std::packaged_task t (func); + std::future f = t.get_future (); + + if (m_thread_count == 0) + { + /* Just execute it now. */ + t (); + } + else + { + std::lock_guard 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 t; + + { + /* We want to hold the lock while examining the task list, but + not while invoking the task function. */ + std::unique_lock 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 00000000000..e1fcb38a581 --- /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 . */ + +#ifndef GDBSUPPORT_THREAD_POOL_H +#define GDBSUPPORT_THREAD_POOL_H + +#include +#include +#include +#include +#include +#include +#include +#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 post_task (std::function 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 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> 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 */ -- 2.30.2