Introduction to Concurrent Programming
Ordinary programming manipulates data the program will use later. Metaprogramming manipulates information about types while the compiler works, so that the eventual program is shaped by those answers.
Why Coordination Takes So Much Attention
Concurrent programming runs multiple tasks at the same time. The idea sounds simple: you have two pieces of work that could happen together, such as computing one result while another piece of computation proceeds on its own. The difficulty is that the parts must cooperate correctly, and that cooperation can demand a lot of attention from the programmer.
What do you think happens?
In many concurrent programs, where does most of the effort go: into the work itself, or into managing how the parts communicate?
Reveal answer
Mostly into managing how the parts communicate
The source notes that in many concurrent programs a large share of the effort goes into managing how the parts communicate, not into the work itself. This article looks at one way that burden can be reduced, using packaged_task.
As you read, keep one question in mind: what does the programmer have to think about so the two pieces cooperate correctly? The goal is to notice where your attention ends up when packaged_task is used.
Two Tasks on Separate Threads
The passage this article draws on describes a case with exactly two tasks. Each task is placed on its own thread. Because the tasks sit on separate threads, they can be run potentially in parallel, meaning the runtime may execute them at the same moment rather than one after the other.
The word potentially matters. The arrangement makes parallel execution possible, but it does not promise that the two tasks will always overlap in time. The runtime decides how they actually run, so your code must be correct whether they overlap or not.
Tracing Two Possible Runs
Consider two tasks: Task A computes one value and Task B computes another, each on its own thread. Trace what the runtime might do with them. The first possibility is that both tasks run at the same moment, so their work overlaps in time. The second possibility is that one task runs to completion before the other begins. Both are legitimate outcomes of the same arrangement.
Reasoning about two possible schedules
Two tasks have been placed on separate threads. Describe what could happen when the runtime executes them.
- 1Identify the arrangement
Each task is tied to its own thread, so the two threads together make overlapping execution possible.
- 2Consider the overlapping schedule
The runtime may execute the two tasks at the same moment, so their steps interleave in time.
- 3Consider the sequential schedule
The runtime may also execute one task before the other, with no overlap at all.
- 4Draw the conclusion
Because either schedule is possible, the code must not depend on which one occurs.
Parallel execution is possible on separate threads, but overlap is not guaranteed, so the programmer must reason about every possible schedule.
How packaged_task Carries a Result
packaged_task lets the programmer concentrate on the tasks to be done rather than on the mechanisms used to manage their communication. The code is written in terms of the tasks. The passage points out that the code it presents never explicitly mentions locks, even though the two tasks run on separate threads and may run in parallel.
The diagram shows the path a result takes. The programmer describes the work, and the packaged_task arrangement handles how the result reaches the reader. The programmer still has to think about what the result path requires, because the coordination has not disappeared. It has simply moved out of the main code you write.
What You Gain and What You Still Carry
- You gain: attention moves toward the tasks to be done rather than the mechanisms that manage their communication.
- You still carry: the responsibility to reason about parallel execution, because two tasks on separate threads may run at the same moment.
- You still carry: the knowledge that coordination exists underneath, even when the code never names it.
Common mistake Assuming that no named locks means no coordination
What it looks likeA code listing with two tasks on separate threads and no lock appears in the code, so the learner concludes that nothing is being coordinated.
Why it failsNot naming locks in code signals where the focus lies. It does not mean coordination is absent from the system.
Do this insteadKeep in mind that threads, communication, and coordination still exist beneath the task-level code.
Common mistake Treating parallel execution as guaranteed
What it looks likeWriting code that assumes the two tasks will always overlap in time.
Why it failsTwo tasks on separate threads can run potentially in parallel, which makes parallel execution possible but not guaranteed.
Do this insteadWrite code that is correct whether the tasks overlap or run one after the other.
Common mistake Believing that packaged_task removes the need to think about communication at all
Compile-Time Computation Explained
C++ includes mechanisms for compile-time computation. These are features that let part of the work of a program happen when the compiler translates the source code, not when the resulting program runs. The benefit described for these mechanisms is tighter type checking and better performance than would otherwise have been possible.
The stages read in order. The compiler reads the code, evaluates the compile-time questions the code asks, and only then produces a program. By the time the program starts running, those answers are already fixed.
Metaprogramming is the use of C++ features for compile-time computation, and template metaprogramming is the form that involves templates. The word meta points at the idea that the program is computing about other code, specifically about types, instead of only computing ordinary values.
Ordinary programming manipulates data the program will use later. Metaprogramming manipulates information about types while the compiler works, so that the eventual program is shaped by those answers.
Type Functions as Questions About Types
Think of a type function as a question that takes a type and gives back an answer, and the answer is determined while the compiler runs. The question might ask whether a type is an integer type, or what kind of element a container's iterator refers to. The result is then available to the rest of the code as a fixed fact.
Refusing a type that does not fit
You are writing a function that only makes sense for whole numbers. You want the program to refuse to compile if someone passes it a type such as double, rather than failing in a confusing way later.
- 1Name the need
The function needs to know, before the program exists, whether the type it receives is a whole-number type.
- 2Ask the question at compile time
A type function takes the type as input and answers the question while the compiler is still working.
- 3Act on the answer
If the answer is no for a type such as double, the compiler can reject the code early.
Because the answer is produced during compilation, bad code can be rejected early, which is the tighter checking the source describes.
Reading Types with Predicates and iterator_traits
The source presents two facilities the standard library provides as concrete examples of this compile-time reasoning: iterator_traits and type predicates. They are worth separating in your mind. One reports facts about a type, and the other tests a yes or no question about it.
| Facility | What it gives you | Shape of the answer |
|---|---|---|
| iterator_traits | Information about an iterator type | Reports facts about the type |
| Type predicate | Checks a property of a type | A true or false answer |
Two standard library facilities for reading type information
Practice Reading Type Questions
A function should only accept whole-number types. Before reading further, decide which kind of tool answers the question 'is this type a whole-number type?' and which kind would report named information about an iterator type. Explain your choice in one or two sentences.
Hints
- A predicate gives a true or false answer about a type.
iterator_traitsreports information about an iterator type rather than answering a yes or no question.
Two tasks are placed on separate threads. Describe two different schedules the runtime might produce, and explain why the code you write must be correct under both.
Hints
- Consider overlap and sequential execution as two possibilities.
- The word potentially means parallel execution is possible but not promised.
Key Takeaways
- Concurrent programming runs multiple tasks at the same time, and coordinating them can demand a lot of attention from the programmer.
packaged_tasklets you concentrate on the tasks to be done, but coordination still exists underneath even when locks are not named in code.- Two tasks on separate threads can run potentially in parallel, so correct code must not depend on whether they overlap.
- Metaprogramming uses C++ features to compute about types at compile time, and type functions answer questions about types before the program runs.
- Type predicates give true or false answers about a type, while
iterator_traitsreports information about an iterator type.