Concepts / Introduction to Concurrent Programming

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.

  • Programming

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?

  • Mostly into the work itself

  • Mostly into managing how the parts communicate

Reveal answer
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.

Task A on Thread 1Possible overlap intimePoint where one waitsTask B on Thread 2runsrunswhere needed
When two tasks run on separate threads, which parts of their execution overlap in time, and where must they wait for each 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.

Worked example

Reasoning about two possible schedules

Two tasks have been placed on separate threads. Describe what could happen when the runtime executes them.

  1. 1
    Identify the arrangement

    Each task is tied to its own thread, so the two threads together make overlapping execution possible.

  2. 2
    Consider the overlapping schedule

    The runtime may execute the two tasks at the same moment, so their steps interleave in time.

  3. 3
    Consider the sequential schedule

    The runtime may also execute one task before the other, with no overlap at all.

  4. 4
    Draw the conclusion

    Because either schedule is possible, the code must not depend on which one occurs.

Result

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.

Task bodypackaged_taskResult valueFutureCaller reads resultwrapped asproducesdelivered toread by
How does a value produced by the task travel from the packaged_task to the future that the caller reads, and what must the programmer still manage along that path?

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 like

    A 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 fails

    Not naming locks in code signals where the focus lies. It does not mean coordination is absent from the system.

    Do this instead

    Keep in mind that threads, communication, and coordination still exist beneath the task-level code.

  • Common mistake

    Treating parallel execution as guaranteed

    What it looks like

    Writing code that assumes the two tasks will always overlap in time.

    Why it fails

    Two tasks on separate threads can run potentially in parallel, which makes parallel execution possible but not guaranteed.

    Do this instead

    Write 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.

Compiler reads thecodeCompile-timequestions evaluatedProgram producedProgram runs withfixed answersthenthenthen
Which steps happen during compilation and which happen when the program runs, and where does metaprogramming fit in that sequence?

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.

Definition

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.

Key point

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.

Input typefor example doubleType functionasks a questionAnswerfixed during compilationCompile accepted orrejectedpassed inproducesused to decide
What goes into a type function as input, what question does it evaluate, and what answer comes out during compilation?
Worked example

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.

  1. 1
    Name the need

    The function needs to know, before the program exists, whether the type it receives is a whole-number type.

  2. 2
    Ask the question at compile time

    A type function takes the type as input and answers the question while the compiler is still working.

  3. 3
    Act on the answer

    If the answer is no for a type such as double, the compiler can reject the code early.

Result

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.

Iterator typevalue_typea reported propertyiterator_categorya reported propertyreportsreports
Given an iterator type, which named properties such as value_type or iterator_category does iterator_traits map to?
FacilityWhat it gives youShape of the answer
iterator_traitsInformation about an iterator typeReports facts about the type
Type predicateChecks a property of a typeA true or false answer

Two standard library facilities for reading type information

Practice Reading Type Questions

EASY

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_traits reports information about an iterator type rather than answering a yes or no question.
MEDIUM

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_task lets 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_traits reports information about an iterator type.

Related concepts

More concepts starting with I