Concurrent Tasks and Futures
When you first write concurrent code, a natural assumption is that two tasks running at the same time will each do their own job cleanly, and their results will simply appear in some order.
Why Two Correct Tasks Can Still Go Wrong
When you first write concurrent code, a natural assumption is that two tasks running at the same time will each do their own job cleanly, and their results will simply appear in some order. If two functions each print a message, you might expect two complete messages to come out, one after the other. That assumption feels reasonable, but for shared objects it is wrong. Threads can freely reach the same objects, and when they do so without coordination, the program's behavior can no longer be read off from the source code.
What do you think happens?
Two tasks each write a message to cout at the same time, with no coordination between them. Will the printed output be the two messages intact, in one of two orders?
Reveal answer
The second option. The source does not promise either clean outcome. It says the output would be unpredictable and could differ from one execution to the next.
The source gives a concrete garbled line, PaHerallllel o World!, as an example of what can appear. Both tasks were individually correct; the problem comes from the unsynchronized shared access between them.
Where Threads and Processes Keep Their Data
The threads of a single program share one address space. This is the key fact that makes data sharing between threads possible. Because threads live inside the same address space, they can communicate by reading and writing shared objects. Processes are different: they generally do not directly share data with one another. Each process is kept apart from the others, so a process cannot simply reach into another process's data the way a thread can reach a shared object.
Read the diagram as a question about visibility. Thread A and Thread B both sit inside the same address space, so both can reach the shared object. Process 1 and Process 2 each have their own space, and the source says they generally do not directly share data with one another. This difference is why threads are the natural way to let one part of a program hand information to another part running at the same time.
Comparing Threads and Processes for Data Sharing
| Feature | Threads of one program | Separate processes |
|---|---|---|
| Address space | One address space shared by all threads | Generally separate from one another |
| Direct data sharing | Yes, through shared objects | Generally no direct data sharing |
| Coordination need | Shared objects must be controlled, for example with locks | Not described in the source as sharing the same objects |
| Typical risk | Data races on shared objects such as cout | Not the shared-object race described in the source |
Threads and processes differ mainly in whether they share an address space.
Shared Objects Like cout
The source gives a concrete pair of tasks to study. One is a plain function f, and the other is a function object F, which is a struct with a call operator. Both tasks write to cout, the standard output stream. What the two tasks have in common is that each one reaches the same shared object. Each task on its own is correct and simple. The difficulty appears only when both tasks run concurrently and touch cout with no coordination between them.
Each task assumes it has the stream to itself, and neither task is written to wait for the other. This is the core of the problem: the code of each task looks fine when read alone. Whether the combined program behaves well depends on how the two tasks overlap in time, which is not visible from either task's text.
Tracing an Interleaved Write
To see why the output breaks, ask what would have to be true for the words to come out clean. Each task's whole write would need to finish before the other task began its write. The program contains nothing that enforces that ordering, so you cannot rely on it. The reason is that the order of execution of the individual operations in the two tasks is not defined. Each task performs a sequence of smaller steps when it writes, and those steps from the two tasks can be mixed together in many different ways.
Explaining the Garbled Line
Two tasks each write a message to cout concurrently. The printed line is PaHerallllel o World!. Explain what this tells you about the writes.
- 1Identify the intended messages
The two intended messages are Hello and Parallel World!.
- 2Look for the pieces
The printed line contains pieces of both messages, with some letters repeated and others out of place.
- 3Conclude about the writes
The two writes were not kept apart. Their steps were woven together on the shared stream.
- 4Locate the cause
The garbling is not a bug in either task's logic. It comes from the unsynchronized shared access itself.
The line is the visible sign of a data race: uncontrolled concurrent access to cout produced output that neither task wrote on its own.
What a Data Race Actually Is
A data race is uncontrolled concurrent access to a variable, and its order of execution is not defined by the program. The definition has two parts: concurrency and the absence of control. Two threads touching the same object is not a data race by itself if their access is properly controlled. The problem arises when both threads reach the shared object at overlapping times and nothing governs who goes first or how far each gets before the other continues.
Why the output cannot be predicted in advance: the program does not define the order in which the individual operations of concurrent tasks run, so the same source code can produce different output on different executions.
Why Locks Restore Control
Because sharing is a double-edged feature, the same convenience that lets one thread hand information to another also means any thread can change an object that another thread is relying on. For that reason, communication through shared objects is typically controlled by locks or other synchronization mechanisms. The control is not optional decoration; it is what keeps the sharing reliable. The source states that this applies even to simple programs like the cout example.
Trace the diagram from left to right. Thread A acquires the lock and uses the shared object while the lock is held. Thread B, trying to enter during that time, is in the waiting state. When Thread A releases the lock, the shared object is free again. This is the controlled alternative to the uncontrolled overlap that produced the garbled line: access is governed by who holds the lock, rather than by chance.
Mistakes Learners Make About Concurrent Access
Common mistake Assuming concurrent access to shared variables is always safe
What it looks likeTwo tasks write to cout at the same time and expect two clean messages.
Why it failsSafety comes from control, not from the mere presence of multiple threads. Uncoordinated overlap can garble the output.
Do this insteadTreat any shared object reached by several threads as needing a synchronization mechanism such as a lock.
Common mistake Judging a concurrent program by reading each task alone
What it looks likeEach function looks correct and simple, so the combined program is assumed correct.
Why it failsEach task is correct on its own, but the behavior depends on how the two tasks overlap in time, which the individual code does not show.
Do this insteadReason about both tasks together and ask what ordering the program actually enforces.
Common mistake Believing threads and processes share data in the same way
Why it failsThreads of one program share one address space. Processes generally do not directly share data.
Do this insteadRemember that shared-object races are a thread-level concern inside one address space.
Common mistake Treating any two threads touching one object as a data race
Why it failsThe definition requires uncontrolled access. Two threads touching the same object is not by itself a data race if access is properly controlled.
Do this insteadCheck whether access is controlled and whether the threads can overlap without anything governing their order.
Practice: Reasoning About Shared Output
Two tasks each write a whole message to cout. Task 1 writes Hello and Task 2 writes Parallel World!. Without any coordination, is it guaranteed that either message appears intact? Explain what would have to be true for both messages to come out clean, and name the mechanism the source identifies for controlling this shared access.
Hints
- Think about what the source says must finish before the other task begins its write.
- The source names locks as a common synchronization mechanism for controlling shared access.
Key Takeaways
- Threads of one program share a single address space, so they can communicate by reading and writing shared objects, while processes generally do not directly share data.
- Two correct tasks that both write to cout without coordination can produce unpredictable, interleaved output, such as PaHerallllel o World!.
- A data race is uncontrolled concurrent access to a variable, and the program does not define its order of execution, so its output cannot be predicted in advance.
- Shared access is made reliable by controlling it with locks or other synchronization mechanisms, and this is needed even in simple programs.