Asynchronous Task Execution with async()
Task-based parallelism treats each task as a function that may run concurrently with other tasks.
Why Independent Jobs Invite Parallel Work
Imagine a program that must perform several jobs, where none of those jobs needs to wait on another. A natural question follows: could the jobs be handed out so that they make progress at the same time, rather than one after another? Parallel programming is the practice of answering that question in code. This article focuses on one particular answer, called task-based parallelism, and on the idea that makes it approachable. The title names async(), the standard library facility through which work is launched as a task, and the model it belongs to is what this article explains.
What do you think happens?
Suppose three jobs never wait on each other. Will they definitely run at the same instant once the program starts them?
Reveal answer
No, the model permits overlap but does not promise it.
The source describes concurrency as a permitted possibility, not a guaranteed schedule. Independence makes overlap allowed; the actual timing is left to how the program and system execute the work.
Shifting the Focus From Threads to Work
The important move in task-based parallelism is a change in viewpoint. Instead of thinking about threads, schedules, or shared variables first, you think about the work itself, packaged as a function. Each function is a self-contained piece of work. You describe what that piece does, and the program is then free to run it alongside other pieces of work.
Task-based parallelism treats each task as a function that may run concurrently with other tasks.
The model is deliberately simple. A task is just a function, and the parallel part is the fact that several such functions may be running at once. The programmer's main job is to describe each piece of work clearly and independently, so that the program has freedom to run those pieces together.
Tracing Three Independent Jobs
Suppose a program has three independent pieces of work: one task that prepares a report, one that resizes a set of images, and one that checks a file for errors. In the task-based view, each of these becomes a function. The program starts them, and while any of them may be running, the others may be running too. When they all finish, the program can gather their results.
Read the diagram as a description of possibility rather than a timetable. The three task boxes sit side by side because they are independent of one another and may run at the same time. The overlap is permitted, and the actual interleaving is left to execution. The join point, where results are collected, is where the program must wait for the work it depends on.
Serial Order Versus Permitted Overlap
The difference between a serial program and a task-based one is what the program is allowed to do. A serial program handles one job, then the next. A task-based program describes the same three jobs as independent functions, which makes overlap possible. The comparison below shows the same three jobs under the two readings.
Concurrency as a Possibility, Not a Schedule
The source is careful about its wording: a task may happen to run concurrently. The model does not promise that every task overlaps with every other task. It allows overlap. Whether two particular tasks actually run at the same instant depends on how the program and the system execute them. A reader should therefore treat concurrency as an opportunity the model creates, not a schedule the programmer fully controls.
This is also why the join point matters. Since the order of finishing is not fixed by the model, the program cannot assume which task completes first. It waits at the point where it depends on the results, and only then gathers them.
Where Shared Memory Styles Fit
Task-based parallelism is one of several ways the C++ standard library supports parallel work. The library supports task-based parallelism and also supports other models beyond it. The source does not list those other models, so this article does not describe them in detail. What matters here is the relationship: task-based parallelism is a supported choice, not the only one.
Shared memory means several units of work can see the same data. That sharing is what makes shared memory styles powerful, and it is also what makes them harder to get right.
The source calls shared memory programming a more subtle and tricky alternative, and suggests it be used as needed rather than as a default. The task model is the approachable starting point because each task is written as a function that describes its own work, while shared memory styles ask you to reason about data that several units of work may touch.
Matching a Problem to the Task Model
The three-job example fits the task model because none of the jobs needs to wait on another. Deciding fit comes down to that property. The table summarizes how the two situations differ according to the source's description.
| Question | Task-based model | Shared memory styles |
|---|---|---|
| Unit of work | A function describing a self-contained piece of work | Units of work that see the same data |
| Dependence between jobs | Fits jobs that do not need to wait on each other | Needed when work must cooperate over shared data |
| Source's guidance | A supported, approachable model | More subtle and tricky, used as needed rather than by default |
| Main thinking shift | Think about the work first | Think carefully about what data is shared |
How the task model and shared memory styles differ, as described in the source
When a problem's jobs are independent and each can be described as a function, the task model is the natural first choice. When the work depends on sharing data among units of work in a way the task model does not describe, a different model supported by the standard library may be needed.
Mistakes When Reasoning About Tasks
Common mistake Treating concurrency as guaranteed
What it looks likeAssuming the report, resize, and check tasks will all be running at the same instant as soon as they are started.
Why it failsThe model says a task may run concurrently. It allows overlap but does not promise a particular schedule.
Do this insteadDescribe the tasks so that they are correct whether they overlap or not, and wait at the join point for the results you need.
Common mistake Assuming any job can be a task
What it looks likeTreating a job that must wait on another job's result as if it were independent.
Why it failsThe task model fits problems where jobs do not need to wait on one another. Dependence changes the picture.
Do this insteadCheck whether each job needs another's output before deciding the task model fits.
Common mistake Reaching for shared memory by default
What it looks likeLetting several units of work modify the same data because it feels natural.
Why it failsThe source describes shared memory styles as subtle and tricky, to be used as needed rather than as a default.
Do this insteadStart from the task model and introduce shared data only when the problem actually requires it.
Practice Deciding Task Fit
A program must (a) generate a summary of last month's sales, (b) compress a folder of log files, and (c) wait for (a) to finish before emailing its results. Which of these jobs fit the task-based model as independent functions, and which dependency needs to be respected at a join point?
Hints
- Ask whether each job needs to wait on another job's result.
- The email step depends on the summary, so it must come after the join point for that dependency.
- Jobs with no dependency between them are the ones that may overlap.
Key Takeaways
- Task-based parallelism treats each unit of work as a function, and the program may run several such functions concurrently.
- Concurrency is a permitted possibility, not a guaranteed schedule; the model allows overlap but does not promise it.
- Independent jobs that do not need to wait on each other fit the task model well, and results are gathered at a join point.
- The C++ standard library supports task-based parallelism alongside other models, and shared memory styles are subtle and used as needed rather than by default.