Learn how futures, agents, executors, and core.async interact with thread pools, and how to size pools by workload type without creating starvation.
Thread pool: A bounded execution resource that decides how many tasks run at once and how much waiting is allowed behind them.
Thread pools are where abstract concurrency decisions become operational behavior. A system can look elegant in code and still collapse under load because blocking work, CPU-bound work, and channel parking all ended up competing on the wrong pool.
Before sizing or tuning any pool, classify the workload:
go processes want a pool reserved for non-blocking coordinationMost thread-pool mistakes come from mixing these categories carelessly.
Different Clojure abstractions rely on different execution models:
future runs work asynchronously using executor-backed threadscore.async/go uses a fixed pool intended for parking, not blockingthread in core.async runs work on separate real threads suitable for blockingThat means the abstraction you choose is already a pool decision, even if you never configure one directly.
Never treat the go pool like a generic worker pool. If blocking I/O runs there, the whole topology can stall. This is one of the most important Clojure concurrency rules and explains many mysterious “async” slowdowns.
A thread-pool size is not just tuning. It encodes policy:
Big queues often delay failure instead of solving it.
Many concurrency bugs become easier to diagnose once workloads are separated:
go-based parking left to the pool intended for itThis isolation prevents one workload type from starving another and makes metrics more meaningful because queue length and saturation actually describe one kind of work.
Good pool review asks:
Without those answers, tuning becomes guesswork.
A queue is not neutral. It decides whether overload becomes visible immediately or is converted into delayed latency. Short queues can fail fast and trigger backpressure sooner. Large queues can make dashboards look calm while requests sit waiting for work that will never finish in time.
That is why thread-pool review should ask two separate questions:
When a Clojure service feels unpredictably slow, the cause is often not the language itself. It is that blocking work, CPU work, and parking coordination were mixed without respecting their pool boundaries.
Use thread pools as explicit workload boundaries, not invisible plumbing. Separate blocking work from parking coordination and from CPU-bound computation. Once those categories are isolated, performance problems become much easier to understand.