Wildmeshing Toolkit
Loading...
Searching...
No Matches
Public Member Functions | Static Public Member Functions | Private Member Functions | Static Private Member Functions | Private Attributes | List of all members
wmtk::threading::detail::worker_pool Class Reference

The process-wide set of threads that task_group runs its tasks on. More...

#include <task_group.hpp>

Public Member Functions

void submit (std::unique_ptr< job > j)
 Run job on a worker. Throws (and queues nothing) if a needed worker cannot be started.
 
bool try_submit (std::unique_ptr< job > &j)
 
size_t worker_count ()
 How many workers the pool has started. For tests.
 

Static Public Member Functions

static worker_pool & instance ()
 

Private Member Functions

void work ()
 

Static Private Member Functions

static std::atomic< worker_pool * > & current ()
 

Private Attributes

std::mutex m_mutex
 
std::condition_variable m_cv
 
std::deque< std::unique_ptr< job > > m_jobs
 
size_t m_idle = 0
 workers that are free for a job: waiting for one, or about to
 
size_t m_spawning = 0
 workers being started, each owed to the job that started it
 
size_t m_workers = 0
 workers started
 

Detailed Description

The process-wide set of threads that task_group runs its tasks on.

Every pass of the optimizers runs as a task_group of NUM_THREADS tasks, and task_group used to spawn a fresh std::thread per task and join it at the barrier. Thread creation itself is cheap; what it threw away was every thread_local the workers had built. Each pass started on threads that had never run an operation, so each of them rebuilt its per-thread polysolve solver – Solver::create validates its JSON parameters against the full spec, and at 16 threads on Thingi10K 103197 that was 12% of all thread time – and re-zeroed its ring-lock scratch, sized to the vertex capacity. A pass of a few milliseconds paid that start-up again every time.

The workers here are started once and kept, so a thread's caches survive from one pass to the next exactly as they already did in serial mode, where one thread runs everything.

SEMANTICS ARE THOSE OF A THREAD PER TASK. A task submitted while no worker is free for it gets a new worker; it never waits in a queue behind tasks that are still running. So a task may block on another task of the same or of another group, a task may run a task_group of its own, and several threads may submit at once, exactly as before – a fixed-size pool would deadlock on the first two. Every queued job is owed an idle worker, or a worker being started for it, and no other job can take that worker.

A worker counts as idle again before its task is reported finished, so a caller that starts its next group as soon as wait() returns finds every worker of the last one free. With one thread submitting – every caller in this repository – the pool therefore never grows past the largest number of tasks that were outstanding at once, which is NUM_THREADS.

The workers are never joined: they block on the condition variable for the life of the process, and the pool is deliberately leaked so that nothing in it is destroyed while a detached worker could still touch it.

A forked child (POSIX) inherits the pool's memory but none of its threads, and possibly its mutex in a locked state. The child therefore abandons it and starts a fresh pool on its first task – as it would have started fresh threads before the pool existed.

Member Function Documentation

◆ try_submit()

bool wmtk::threading::detail::worker_pool::try_submit ( std::unique_ptr< job > &  j)
inline

Queue j on a worker that is idle right now, as submit() would, and return true; or, if every idle worker is already owed to a queued job, return false with j left untouched. It never starts a worker.


The documentation for this class was generated from the following file: