Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or https://www.opensource.org/licenses/mit-license.php.5#ifndef BITCOIN_UTIL_THREADPOOL_H6#define BITCOIN_UTIL_THREADPOOL_H8#include <sync.h>9#include <tinyformat.h>10#include <util/check.h>11#include <util/expected.h>12#include <util/thread.h>14#include <algorithm>15#include <condition_variable>16#include <functional>17#include <future>18#include <queue>19#include <ranges>20#include <thread>21#include <type_traits>22#include <utility>23#include <vector>25/**26* @brief Fixed-size thread pool for running arbitrary tasks concurrently.27*28* The thread pool maintains a set of worker threads that consume and execute29* tasks submitted through Submit(). Once started, tasks can be queued and30* processed asynchronously until Stop() is called.31*32* ### Thread-safety and lifecycle33* - `Start()` and `Stop()` must be called from a controller (non-worker) thread.34* Calling `Stop()` from a worker thread will deadlock, as it waits for all35* workers to join, including the current one.36*37* - `Submit()` can be called from any thread, including workers. It safely38* enqueues new work for execution as long as the pool has active workers.39*40* - `Interrupt()` stops new task submission and lets queued ones drain41* in the background. Callers can continue other shutdown steps and call42* Stop() at the end to ensure no remaining tasks are left to execute.43*44* - `Stop()` prevents further task submission and blocks until all the45* queued ones are completed.46*/47class ThreadPool48{49private:50std::string m_name;51Mutex m_mutex;52std::queue<std::packaged_task<void()>> m_work_queue GUARDED_BY(m_mutex);53std::condition_variable m_cv;54// Note: m_interrupt must be guarded by m_mutex, and cannot be replaced by an unguarded atomic bool.55// This ensures threads blocked on m_cv reliably observe the change and proceed correctly without missing signals.56// Ref: https://en.cppreference.com/w/cpp/thread/condition_variable57bool m_interrupt GUARDED_BY(m_mutex){false};58std::vector<std::thread> m_workers GUARDED_BY(m_mutex);60void WorkerThread() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)61{62WAIT_LOCK(m_mutex, wait_lock);63for (;;) {64std::packaged_task<void()> task;65{66// Wait only if needed; avoid sleeping when a new task was submitted while we were processing another one.67if (!m_interrupt && m_work_queue.empty()) {68// Block until the pool is interrupted or a task is available.69m_cv.wait(wait_lock, [&]() EXCLUSIVE_LOCKS_REQUIRED(m_mutex) { return m_interrupt || !m_work_queue.empty(); });70}72// If stopped and no work left, exit worker73if (m_interrupt && m_work_queue.empty()) {74return;75}77task = std::move(m_work_queue.front());78m_work_queue.pop();79}81{82// Execute the task without the lock83REVERSE_LOCK(wait_lock, m_mutex);84task();85}86}87}89public:90explicit ThreadPool(const std::string& name) : m_name(name) {}92~ThreadPool()93{94Stop(); // In case it hasn't been stopped.95}97/**98* @brief Start worker threads.99*100* Creates and launches `num_workers` threads that begin executing tasks101* from the queue. If the pool is already started, throws.102*103* Must be called from a controller (non-worker) thread.104*/105void Start(int num_workers) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)106{107assert(num_workers > 0);108LOCK(m_mutex);109if (m_interrupt) throw std::runtime_error("Thread pool has been interrupted or is stopping");110if (!m_workers.empty()) throw std::runtime_error("Thread pool already started");112// Create workers113m_workers.reserve(num_workers);114for (int i = 0; i < num_workers; i++) {115m_workers.emplace_back(&util::TraceThread, strprintf("%s.%02d", m_name, i), [this] { WorkerThread(); });116}117}119/**120* @brief Stop all worker threads and wait for them to exit.121*122* Sets the interrupt flag, wakes all waiting workers, and joins them.123* Any remaining tasks in the queue will be processed before returning.124*125* Must be called from a controller (non-worker) thread.126* Concurrent calls to Start() will be rejected while Stop() is in progress.127*/128void Stop() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)129{130// Notify workers and join them131std::vector<std::thread> threads_to_join;132{133LOCK(m_mutex);134// Ensure Stop() is not called from a worker thread while workers are still registered,135// otherwise a self-join deadlock would occur.136auto id = std::this_thread::get_id();137for (const auto& worker : m_workers) assert(worker.get_id() != id);138// Early shutdown to return right away on any concurrent Submit() call139m_interrupt = true;140threads_to_join.swap(m_workers);141}142m_cv.notify_all();143// Help draining queue144while (ProcessTask()) {}145// Free resources146for (auto& worker : threads_to_join) worker.join();148// Since we currently wait for tasks completion, sanity-check empty queue149LOCK(m_mutex);150Assume(m_work_queue.empty());151// Re-allow Start() now that all workers have exited152m_interrupt = false;153}155enum class SubmitError {156Inactive,157Interrupted,158};160template <class F>161using Future = std::future<std::invoke_result_t<F>>;163template <class R>164using RangeFuture = Future<std::ranges::range_reference_t<R>>;166template <class F>167using PackagedTask = std::packaged_task<std::invoke_result_t<F>()>;169/**170* @brief Enqueues a new task for asynchronous execution.171*172* @param fn Callable to execute asynchronously.173* @return On success, a future containing fn's result.174* On failure, an error indicating why the task was rejected:175* - SubmitError::Inactive: Pool has no workers (never started or already stopped).176* - SubmitError::Interrupted: Pool task acceptance has been interrupted.177*178* Thread-safe: Can be called from any thread, including within the provided 'fn' callable.179*180* @warning Ignoring the returned future requires guarding the task against181* uncaught exceptions, as they would otherwise be silently discarded.182*/183template <class F>184[[nodiscard]] util::Expected<Future<F>, SubmitError> Submit(F&& fn) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)185{186PackagedTask<F> task{std::forward<F>(fn)};187auto future{task.get_future()};188{189LOCK(m_mutex);190if (m_workers.empty()) return util::Unexpected{SubmitError::Inactive};191if (m_interrupt) return util::Unexpected{SubmitError::Interrupted};193m_work_queue.emplace(std::move(task));194}195m_cv.notify_one();196return {std::move(future)};197}199/**200* @brief Enqueues a range of tasks for asynchronous execution.201*202* @param fns Callables to execute asynchronously.203* @return On success, a vector of futures containing each element of fns's result in order.204* On failure, an error indicating why the range was rejected:205* - SubmitError::Inactive: Pool has no workers (never started or already stopped).206* - SubmitError::Interrupted: Pool task acceptance has been interrupted.207*208* This is more efficient when submitting many tasks at once, since209* the queue lock is only taken once internally and all worker threads are210* notified. For single tasks, Submit() is preferred since only one worker211* thread is notified.212*213* Thread-safe: Can be called from any thread, including within submitted callables.214*215* @warning Ignoring the returned futures requires guarding tasks against216* uncaught exceptions, as they would otherwise be silently discarded.217*/218template <std::ranges::sized_range R>219requires(!std::is_lvalue_reference_v<R>)220[[nodiscard]] util::Expected<std::vector<RangeFuture<R>>, SubmitError> Submit(R&& fns) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)221{222std::vector<RangeFuture<R>> futures;223futures.reserve(std::ranges::size(fns));225{226LOCK(m_mutex);227if (m_workers.empty()) return util::Unexpected{SubmitError::Inactive};228if (m_interrupt) return util::Unexpected{SubmitError::Interrupted};229for (auto&& fn : fns) {230PackagedTask<std::ranges::range_reference_t<R>> task{std::move(fn)};231futures.emplace_back(task.get_future());232m_work_queue.emplace(std::move(task));233}234}235m_cv.notify_all();236return {std::move(futures)};237}239/**240* @brief Execute a single queued task synchronously.241* Removes one task from the queue and executes it on the calling thread.242*/243bool ProcessTask() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)244{245std::packaged_task<void()> task;246{247LOCK(m_mutex);248if (m_work_queue.empty()) return false;250// Pop the task
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.