a task and the task queue
Think of a deli counter with a 'take a number' dispenser. Each customer is a unit of work; the numbered tickets sitting in the tray, in order, are the waiting list. The staff pull tickets front-to-back. A task is one such unit of work — a self-contained chunk of computation packaged so it can run later. The task queue is the tray that holds tasks waiting for a worker to pick them up.
A task (also called a work item or a job) is, in code, usually a closure or a small object: a function to call plus the data it needs (its captured arguments). It deliberately carries no thread of its own — it is just a description of work, inert until some worker runs it. The task queue is a concurrent first-in-first-out structure that producers push tasks onto and worker threads pop tasks off of. Because many threads touch it at once, it must be safe for concurrent access — protected by a lock, or built as a lock-free queue. A simple pool has one shared queue; fancier schedulers give each worker its own queue (a deque) and let idle workers steal from others.
The task/queue split is the core idea that lets you separate 'what work exists' from 'which thread runs it, and when'. It shows up everywhere: a thread pool's backlog, an event loop's pending callbacks, a GPU command queue, a message broker. The key mental shift for a beginner: a task is data, not an activity. Submitting a task does not start anything by itself; it only places work where a worker will eventually find it. If no worker ever drains the queue, tasks sit there forever.
A task as a closure (Rust): let task = move || { process(data); }; pool.spawn(task); // 'task' is just a value until a worker calls it.
The closure captures its data and is enqueued; nothing runs until a worker pops and invokes it.
An unbounded task queue can hide a memory leak: if producers submit faster than workers drain, the backlog grows without limit until you run out of memory. Bounding the queue (and applying backpressure) is the cure.