How the Parallel Module Works for Multi-Threaded Computations in Nelson
Nelson's parallel module implements multi-threaded computations using a C++17 thread pool that executes functions asynchronously through Future objects, enabling MATLAB-compatible parfeval syntax for background task execution.
The parallel module in Nelson provides asynchronous, multi-threaded evaluation of functions for the nelson-lang/nelson open-source interpreter. By leveraging a custom C++ thread pool and Future-based task management, this module enables users to offload computations to background threads while maintaining a MATLAB-compatible API.
Core Architecture of the Parallel Module
Thread-Pool Implementation with BS::pause_thread_pool
At the foundation lies a header-only C++ thread pool implementation located in [modules/parallel/src/include/BS_thread_pool.hpp](https://github.com/nelson-lang/nelson/blob/master/modules/parallel/src/include/BS_thread_pool.hpp). This file wraps Barak Shoshany's modern C++17/20 thread pool, providing the BS::pause_thread_pool class that Nelson uses for worker thread management.
The pool supports pausing and unpausing operations, which the Nelson runtime employs to temporarily halt background work while the interpreter executes critical sections or undergoes state changes.
Singleton Background Pool Management
Nelson maintains a single global thread pool through the BackgroundPoolObject singleton, defined in [modules/parallel/src/include/BackgroundPoolObject.hpp](https://github.com/nelson-lang/nelson/blob/master/modules/parallel/src/include/BackgroundPoolObject.hpp) and implemented in [modules/parallel/src/cpp/BackgroundPoolObject.cpp](https://github.com/nelson-lang/nelson/blob/master/modules/parallel/src/cpp/BackgroundPoolObject.cpp).
Upon construction, this singleton creates a pause_thread_pool whose size is determined by NelsonConfiguration::getMaxNumCompThreads(), allowing users to configure thread count through Nelson's standard configuration mechanisms.
How Asynchronous Task Execution Works
Future Objects and State Management
Every asynchronous computation in Nelson is represented by a Future object. The base class FutureObject in [modules/parallel/src/include/FutureObject.hpp](https://github.com/nelson-lang/nelson/blob/master/modules/parallel/src/include/FutureObject.hpp) stores:
- The function name and arguments
- The result vector
- Execution state (
QUEUED,RUNNING,FINISHED,CANCELLED) - Time stamps for creation and completion
For function evaluation specifically, Nelson uses FevalFutureObject ([modules/parallel/src/include/FevalFutureObject.hpp](https://github.com/nelson-lang/nelson/blob/master/modules/parallel/src/include/FevalFutureObject.hpp) and [modules/parallel/src/cpp/FevalFutureObject.cpp](https://github.com/nelson-lang/nelson/blob/master/modules/parallel/src/cpp/FevalFutureObject.cpp)), which knows how to execute a Nelson FunctionDef in the background.
Task Submission via feval
The core mechanism for scheduling work resides in BackgroundPoolObject::feval. When you call parfeval from an .m script, it ultimately invokes this C++ method, which submits a lambda to the thread pool:
// Excerpt from BackgroundPoolObject::feval
auto future = threadPool->submit_task(
[retFuture, fptr, nLhs, argIn]() {
retFuture->evaluateFunction(fptr, nLhs, argIn, true);
});
The submit_task method belongs to BS::pause_thread_pool and enqueues the lambda for execution on a worker thread. The lambda captures a newly allocated FevalFutureObject along with the function pointer and arguments, executing evaluateFunction in the background.
Global Future Queue Tracking
Every created future registers with the FevalQueueObject ([modules/parallel/src/include/FevalQueueObject.hpp](https://github.com/nelson-lang/nelson/blob/master/modules/parallel/src/include/FevalQueueObject.hpp) and [modules/parallel/src/cpp/FevalQueueObject.cpp](https://github.com/nelson-lang/nelson/blob/master/modules/parallel/src/cpp/FevalQueueObject.cpp)). This global queue allows the interpreter to:
- Query outstanding futures
- Cancel pending or running tasks
- Retrieve results through
fetchNextorfetchOutputs
MATLAB-Compatible API: parfeval and Background Pool
The parallel module exposes a MATLAB-compatible interface through built-in functions defined in modules/parallel/builtin/include/*Builtin.hpp (such as parfevalBuiltin.hpp). The parfeval function connects .m scripts to the C++ core:
f = parfeval(pool, @myFunction, nLhs, arg1, arg2, ...);
Here, pool is a handle to the singleton background pool obtained via backgroundPool. The call returns a Future handle that supports:
fetchOutputs(f)– Retrieve results (blocks until finished)wait(f)– Block until completion without retrieving resultscancel(f)– Mark the future as cancelledf.State– Query current execution state
Practical Examples
Simple Asynchronous Call
This example demonstrates scheduling a function that sleeps for 2 seconds while the main thread continues working:
% Create a reference to the global background pool
pool = backgroundPool; % built-in handle
% Schedule a function that sleeps 2 seconds and returns the value 42
f = parfeval(pool, @() pause(2); 0, 42); % 0 lhs because we just want the result
% Do other work while the background thread sleeps
disp('Main thread is busy...');
tic;
while ~strcmp(f.State, 'finished')
pause(0.5); % poll every 0.5 s
fprintf('...still waiting (%.1f s elapsed)\n', toc);
end
% Retrieve the result (throws if the future failed)
result = fetchOutputs(f);
fprintf('Result from background thread: %g\n', result);
Relevant sources: parfeval → BackgroundPoolObject::feval (see the submit_task lambda), FevalFutureObject stores the result, fetchOutputs reads it.
Parallel Array of Futures
Run multiple independent computations simultaneously and collect results:
% Run three independent calls in parallel
pool = backgroundPool;
f(1) = parfeval(pool, @sin, 1, pi/6);
f(2) = parfeval(pool, @cos, 1, pi/6);
f(3) = parfeval(pool, @tan, 1, pi/6);
% Wait for all to finish (equivalent to MATLAB's wait for all)
wait(f); % blocks until every future is FINISHED
% Collect results
s = fetchOutputs(f(1));
c = fetchOutputs(f(2));
t = fetchOutputs(f(3));
fprintf('sin=%.3f, cos=%.3f, tan=%.3f\n', s, c, t);
Relevant sources: FevalQueueObject::add (queues each future), wait implementation in Future_waitBuiltin.hpp that repeatedly checks future->state.
Canceling Long-Running Computations
Terminate background tasks that are no longer needed:
pool = backgroundPool;
% Start a heavy loop that never ends unless cancelled
f = parfeval(pool, @() while true; pause(0.1); end, 0);
% Let it run for a second, then cancel
pause(1);
cancel(f); % marks the future as CANCELLED
% Fetch will now raise an exception
try
fetchOutputs(f);
catch e
disp(['Computation cancelled: ' e.message]);
end
Relevant sources: FutureObject::cancel (sets state = CANCELLED), FevalFutureObject::evaluateFunction checks changeState flag and aborts if cancelled.
Key Source Files
The parallel module spans several critical files in the nelson-lang/nelson repository:
-
modules/parallel/src/include/BS_thread_pool.hpp– Header-only C++ thread pool (BS::pause_thread_pool) providing the core worker thread management with pause/unpause capabilities. -
modules/parallel/src/include/BackgroundPoolObject.hppandmodules/parallel/src/cpp/BackgroundPoolObject.cpp– Singleton managing the global thread pool instance and exposing thefevalmethod for task submission. -
modules/parallel/src/include/FutureObject.hpp– Base class defining the Future interface, execution states (QUEUED,RUNNING,FINISHED,CANCELLED), and result storage. -
modules/parallel/src/include/FevalFutureObject.hppandmodules/parallel/src/cpp/FevalFutureObject.cpp– Specialized Future implementation that executes NelsonFunctionDefobjects in background threads. -
modules/parallel/src/include/FevalQueueObject.hppandmodules/parallel/src/cpp/FevalQueueObject.cpp– Global registry tracking all active futures, enablingwait,cancel, and queue inspection operations. -
modules/parallel/src/include/ParallelEvaluator.hppandmodules/parallel/src/cpp/ParallelEvaluator.cpp– Factory for creating per-taskEvaluatorinstances that run in worker threads. -
modules/parallel/builtin/include/*Builtin.hpp(e.g.,parfevalBuiltin.hpp) – Built-in function declarations connecting the Nelson language syntax to the C++ parallel core.
Summary
- Nelson's parallel module utilizes a header-only C++17 thread pool (
BS::pause_thread_pool) defined inBS_thread_pool.hppto manage worker threads. - A singleton BackgroundPoolObject maintains the global thread pool instance, sized according to
NelsonConfiguration::getMaxNumCompThreads(). - Future objects (
FevalFutureObject) encapsulate asynchronous computations, tracking states fromQUEUEDthroughFINISHEDorCANCELLED. - Tasks are submitted via lambda functions to the thread pool through
BackgroundPoolObject::feval, which captures the future and function arguments for background execution. - The FevalQueueObject maintains a global registry of all futures, supporting MATLAB-compatible operations like
wait,cancel, andfetchOutputs. - The module supports pausing and resetting the thread pool to handle interpreter critical sections safely.
Frequently Asked Questions
What thread pool library does Nelson use for parallel computations?
Nelson uses a header-only C++ thread pool implementation located in modules/parallel/src/include/BS_thread_pool.hpp. This is based on Barak Shoshany's modern C++17/20 thread pool and provides the BS::pause_thread_pool class with support for pausing, unpausing, and dynamic resizing of worker threads.
How does Nelson handle thread safety during interpreter critical sections?
The parallel module implements a pause mechanism through BS::pause_thread_pool. When the interpreter enters a critical section or requires temporary thread safety, it calls threadPool->pause() to halt background task processing. Once the critical section completes, threadPool->unpause() resumes normal operation. This approach avoids complex locking by temporarily stopping worker threads rather than implementing fine-grained synchronization.
Can I cancel a computation that is already running in the background?
Yes, the parallel module supports cancellation through the cancel function. When invoked, it calls FutureObject::cancel which sets the future's state to CANCELLED. The FevalFutureObject::evaluateFunction method periodically checks the changeState flag and aborts execution if cancellation is detected, preventing wasted CPU cycles on obsolete computations.
How is the number of parallel threads configured in Nelson?
The thread pool size is determined by NelsonConfiguration::getMaxNumCompThreads(), which reads user configuration settings. The BackgroundPoolObject singleton initializes the pause_thread_pool with this size during construction. Users can also resize the pool dynamically through resetThreadPool, which invokes threadPool->reset(newSize) to adjust the number of worker threads without restarting the interpreter.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →