01 · INTRODUCTION
How Does the CPU Process One Thread of Machine Instructions?
In the previous articles, we built a simple model of an iOS application running inside a process. That process contains threads, and the operating system schedules runnable threads onto the processor so that their work can make progress.
But there is still an important part of the picture missing.
What is actually being processed?
When we say that the CPU is “executing a thread,” it is easy to imagine the processor somehow receiving our Swift code. Perhaps we picture the CPU working its way through viewDidLoad(), an if statement or a line such as counter += 1.
That is not what happens.
The processor does not understand Swift. It does not understand classes, actors, optionals, closures, async, await or any of the other abstractions we use to construct an iOS application.
The processor executes machine instructions.
Understanding that journey from Swift source code to machine instructions is extraordinarily useful when learning concurrency, because it explains why one innocent-looking line of Swift should not automatically be treated as one indivisible operation.
02 · TUTORIAL
Start With a Line of Swift
Imagine that we have a very simple value:
CODE EXAMPLE
var counter = 0
Later in our application we increase it:
CODE EXAMPLE
counter += 1
To us, this looks like one instruction.
There is one line in Xcode, one compound assignment operator and one obvious intention: take the current value of counter and increase it by one.
But Swift is a high-level programming language designed for humans to write and reason about software.
The CPU requires something much lower-level.
Before our application runs, the Swift compiler and associated compilation toolchain transform our program into executable machine code for the target platform.
A useful simplified model is:
EXECUTION DIAGRAM
Swift Source Code
│
▼
Compilation
│
▼
Machine Instructions
│
▼
Application Runs
│
▼
System Threads Execute
│
▼
Processor Cores
This is the first connection to commit to memory.
💡 Important Idea
The CPU does not execute your Swift source code. Your Swift program is compiled into machine instructions that the processor can execute.
03 · TUTORIAL
What Is Machine Code?
Machine code is the low-level set of instructions understood by a particular processor architecture.
At this level we are no longer talking about concepts such as UIViewController, Task, Array or actor. The processor works with much more fundamental operations: moving data, performing arithmetic, comparing values, branching to another instruction, loading values from memory and storing results.
The exact instructions generated from a particular piece of Swift depend on the compiler, optimisation settings, target architecture and surrounding code. We therefore should not pretend that one Swift expression always becomes one exact sequence of machine instructions.
For learning concurrency, however, we can use a conceptual breakdown.
Our Swift:
CODE EXAMPLE
counter += 1
can be thought about as work resembling:
CODE EXAMPLE
LOAD the current value of counter
ADD 1
STORE the new value back
The important lesson is not the precise instruction names.
The important lesson is that the single Swift statement we see in Xcode may require several lower-level operations before its intended effect is complete.
04 · TUTORIAL
The CPU Works Through Instructions
Now imagine that one of our application's threads has been selected by the operating system to execute on a processor core.
Our simplified model from the previous article looked like this:
EXECUTION DIAGRAM
MY APP PROCESS
│
├── Main Thread
├── Thread 2
└── Thread 3
│
▼
OS Scheduler
│
▼
CPU Core
Suppose Thread 2 is currently scheduled.
The processor executes the machine instructions associated with that execution context.
EXECUTION DIAGRAM
THREAD 2
Instruction 1
Instruction 2
Instruction 3
Instruction 4
Instruction 5
Instruction 6
│
▼
CPU CORE
The processor repeatedly works through instructions, changing its internal state and interacting with memory as required by those instructions.
At a deliberately simplified level, you can think of the processor as continually answering:
What instruction should I execute next?
That question is much closer to the hardware than asking which Swift function the CPU is currently “inside.”
05 · TUTORIAL
The Processor Keeps Track of the Next Instruction
For a program to execute sequentially, the processor needs to know where execution currently is and which instruction should come next.
Processors maintain architectural state that includes a location for the current or next instruction. Depending on the architecture and terminology, you will hear this described using names such as the program counter or instruction pointer.
For our mental model, imagine this:
EXECUTION DIAGRAM
Machine Instructions
Address 1000 LOAD value
Address 1004 ADD 1
Address 1008 STORE value
Address 1012 COMPARE result
Address 1016 BRANCH if needed
▲
│
current execution point
The actual details of instruction sizes and addresses vary by architecture, so these numbers are purely illustrative.
The important point is that execution has a location.
This is part of the reason a thread can be paused and later resumed.
The system can preserve the execution state associated with that thread and later restore enough state for execution to continue correctly.
06 · TUTORIAL
Registers Give the Processor Somewhere Extremely Fast to Work
A processor also contains very fast storage locations called registers.
Registers are not the same thing as your application's normal memory. They exist inside the processor and are used while instructions are being executed.
Imagine our counter currently contains the value 41.
A deliberately simplified representation of the work might look like this:
EXECUTION DIAGRAM
Application Memory
counter = 41
│
│ LOAD
▼
Processor Register
value = 41
│
│ ADD 1
▼
Processor Register
value = 42
│
│ STORE
▼
Application Memory
counter = 42
Again, real processors, caches, compiler optimisations and memory systems are more sophisticated than this diagram.
But this simplified model teaches us something extremely important about concurrency.
The complete meaning of:
CODE EXAMPLE
counter += 1
does not necessarily happen as one indivisible event simply because Swift presents it on one line.
07 · TUTORIAL
One Swift Line Is Not Necessarily One Machine Instruction
This is one of the most useful facts to carry into multithreaded programming.
Developers naturally reason at the level of source code.
We see:
CODE EXAMPLE
counter += 1
and mentally package the whole thing together.
We imagine:
EXECUTION DIAGRAM
counter was 41
↓
counter is now 42
But the machine may have to perform multiple operations to achieve that result.
Conceptually:
EXECUTION DIAGRAM
READ 41
↓
ADD 1
↓
WRITE 42
Those intermediate stages matter when more than one execution context can reach the same mutable state.
💡 Important Idea
One line of Swift should not automatically be treated as one atomic processor operation.
08 · TUTORIAL
Now Add a Second Thread
With one thread, our counter example is easy to understand.
EXECUTION DIAGRAM
THREAD 1
READ counter → 41
ADD 1 → 42
WRITE → 42
The final value is 42.
Now imagine two threads within the same application process can access the same mutable value.
EXECUTION DIAGRAM
┌─────────────────────────────────────────┐
│ MY APP PROCESS │
│ │
│ counter = 41 │
│ ▲ ▲ │
│ │ │ │
│ Thread 1 Thread 2 │
│ │
└─────────────────────────────────────────┘
Both threads want to execute:
CODE EXAMPLE
counter += 1
Our intention is obvious.
CODE EXAMPLE
41 + 1 + 1 = 43
But now the ordering of the underlying operations matters.
09 · TUTORIAL
The CPU Does Not Know the Meaning of Your Feature
This is where thinking from the processor upward becomes incredibly useful.
The processor does not know that these two groups of instructions represent two logical “increment counter” operations.
It does not know that counter represents the number of unread messages, rockets, shopping basket items or completed downloads in your application.
It does not understand your product requirement.
It executes machine instructions.
If Thread 1 reads the value:
EXECUTION DIAGRAM
THREAD 1
READ counter → 41
and Thread 2 also reads the value before a correctly synchronised update has occurred:
EXECUTION DIAGRAM
THREAD 1 THREAD 2
READ counter → 41 READ counter → 41
both execution contexts may calculate their new value from the same old value.
EXECUTION DIAGRAM
THREAD 1 THREAD 2
READ 41 READ 41
ADD 1 → 42 ADD 1 → 42
WRITE 42 WRITE 42
We requested two increments.
But the resulting value can still be 42.
The machine has faithfully executed instructions.
The bug exists because our program failed to coordinate concurrent access to shared mutable state.
10 · TUTORIAL
This Is Why Machine Instructions Matter to Concurrency
You do not need to write assembly language to become good at Swift Concurrency.
You do not need to memorise CPU instruction sets.
You do not need to know exactly which registers the compiler chooses for every Swift expression.
But you should understand that your high-level Swift statements are compiled into lower-level operations and that concurrent execution can interact at a level below the neat lines you see in Xcode.
This single piece of understanding explains a surprising amount of concurrency.
It explains why shared mutable state needs protection.
It explains why counter += 1 is not automatically thread-safe.
It explains why code that looks sequential can behave unexpectedly once several execution contexts are involved.
And it explains why concurrency safety cannot be achieved simply by looking at two adjacent lines of Swift and deciding that they “look safe.”
11 · TUTORIAL
What Happens When the OS Switches Threads?
Now we can connect this article directly to the previous one.
Suppose Thread 1 is executing on our simplified single-core processor.
CODE EXAMPLE
THREAD 1
Instruction A
Instruction B
Instruction C
Instruction D
Instruction E
The processor executes some of that work.
EXECUTION DIAGRAM
CPU CORE
Instruction A
↓
Instruction B
↓
Instruction C
The operating system may later schedule another runnable thread.
Enough of Thread 1's execution state is preserved so that it can continue correctly when it runs again.
CODE EXAMPLE
THREAD 1
Instruction A ✓
Instruction B ✓
Instruction C ✓
-----------------
PAUSED
-----------------
Instruction D
Instruction E
Thread 2 can now execute.
CODE EXAMPLE
THREAD 2
Instruction A
Instruction B
Instruction C
Instruction D
Later, Thread 1 may resume and continue its own execution.
This is why thinking of each thread as a stream of executable work is so useful.
The operating system is not moving your Swift source-code editor between functions. It is scheduling execution contexts whose compiled instructions ultimately run on processor cores.
12 · TUTORIAL
The Process Provides the Memory Environment
We can now make our process diagram more meaningful.
EXECUTION DIAGRAM
┌─────────────────────────────────────────────┐
│ MY APP PROCESS │
│ │
│ Application Memory │
│ │
│ counter = 41 │
│ ▲ ▲ │
│ │ │ │
│ Main Thread Thread 2 │
│ │ │ │
└────────────────┼─────┼──────────────────────┘
│ │
▼ ▼
OS Scheduling
│
▼
CPU Cores
The process provides the execution environment and virtual address space belonging to our application.
The threads are execution contexts within that process.
The operating system schedules runnable threads.
The processor cores execute machine instructions.
This gives us the complete foundational chain:
EXECUTION DIAGRAM
iOS Application
↓
Process
↓
Application Memory + Threads
↓
OS Thread Scheduling
↓
Processor Core
↓
Machine Instructions Execute
If you can draw and explain this model, the foundations of concurrency become dramatically easier to reason about.
13 · TUTORIAL
Does the CPU Execute a Process or a Thread?
This is where the terminology can become confusing.
We often say that the operating system is “running a process,” and that is perfectly useful at a high level.
But when we zoom into CPU scheduling, the execution context being scheduled onto a processor core is a thread.
The process supplies the surrounding execution environment: its virtual address space and other process resources.
The thread supplies the execution context through which instructions belonging to that process run.
So our more precise mental model is:
EXECUTION DIAGRAM
PROCESS
contains
│
▼
THREADS
scheduled onto
│
▼
CPU CORES
which execute
│
▼
MACHINE INSTRUCTIONS
This distinction becomes important enough that we will give it an article of its own.
14 · TUTORIAL
What About Multiple CPU Cores?
So far we have deliberately imagined one processor core.
That simplification allows us to see concurrency clearly.
With one core:
EXECUTION DIAGRAM
TIME ─────────────────────────────────────▶
CPU CORE
Thread 1 Thread 2 Thread 1 Thread 3
████████ ████████ ████████ ████████
Several threads make progress over the same period, but only one executes on that core at a particular instant.
Now add another core.
EXECUTION DIAGRAM
TIME ─────────────────────────────────────▶
CORE 1
Thread 1 Thread 2
████████ ████████
CORE 2
Thread 3 Thread 1
████████ ████████
Now some work can genuinely execute simultaneously.
This is where parallelism enters the picture.
But notice that our basic model has not changed.
Threads are still execution contexts.
The operating system still schedules runnable threads.
Processor cores still execute machine instructions.
We simply have more cores capable of executing instructions at the same time.
15 · TUTORIAL
More Cores Do Not Solve Shared-State Problems
It might be tempting to think that additional processor cores somehow make concurrency safer.
They do not.
In fact, genuine parallel execution makes it even more obvious why shared mutable state requires careful coordination.
Two threads may now literally be executing at the same time on different processor cores while accessing state belonging to the same process.
EXECUTION DIAGRAM
MY APP PROCESS
counter = 41
▲ ▲
│ │
Thread 1 Thread 2
│ │
▼ ▼
CORE 1 CORE 2
██████ ██████
The programmer still needs a concurrency model that defines how shared state can be accessed safely.
This is why simply adding processor cores was never going to solve the software architecture problem.
16 · TUTORIAL
Why We Should Not Build Apps by Thinking in Machine Instructions
At this point it might sound as though the answer is for every iOS developer to become a processor expert.
It is not.
Abstraction is one of the reasons modern software development is possible.
We write Swift because we do not want to construct applications by manually arranging processor instructions.
We use UIKit and SwiftUI because we do not want to manually construct every pixel and interaction from primitive operations.
And increasingly, we use Swift Concurrency because we do not want application architecture to be built around manually reasoning about every underlying thread.
The goal of learning the lower layers is not to remain there.
The goal is to understand why the higher-level abstraction exists.
17 · TUTORIAL
This Is Where Swift Concurrency Becomes More Interesting
Traditional concurrency discussions often become dominated by threads.
Which thread am I on?
Should this go onto a background queue?
When do I return to the main queue?
Which lock protects this value?
How many threads are running?
Those questions were understandable because threads were closely connected to the way we reasoned about concurrent execution.
Swift Concurrency gives us a higher-level model.
Instead of making every asynchronous operation conceptually own a thread, we can represent asynchronous work as a Task.
That task can execute, reach a suspension point, stop requiring an execution resource while it waits, and later continue.
Underneath that abstraction, machine instructions still have to reach processor cores.
But our application architecture no longer needs to be designed directly around that machinery.
18 · TUTORIAL
A Swift Task Is Not a New Stream of Machine Instructions Permanently Attached to a Thread
This distinction is extremely important.
When we write:
CODE EXAMPLE
Task {
let launches = try await api.loadLaunches()
update(with: launches)
}
we should not imagine:
EXECUTION DIAGRAM
Create Thread 47
↓
Attach this Task to Thread 47
↓
Keep Thread 47 for the lifetime of the Task
That is not the Swift Concurrency model.
The task represents asynchronous work. Runnable synchronous portions of that work can be arranged for execution by an executor using underlying system execution resources.
When the task reaches a point where it may suspend:
CODE EXAMPLE
let launches = try await api.loadLaunches()
the task may stop executing while it waits.
It still exists.
But it does not need to keep a system thread blocked merely to represent the fact that the asynchronous operation has not finished yet.
19 · TUTORIAL
The Hardware Has Not Changed — Our Programming Model Has
This is perhaps the most important bridge between the machine and Swift Concurrency.
The processor still executes machine instructions.
System threads still exist.
The operating system still schedules those threads onto processor cores.
Memory still exists.
Registers still exist.
None of that disappeared when Apple introduced Swift Concurrency.
What changed is the abstraction we can use to structure asynchronous and concurrent software.
We can increasingly think in terms of:
CODE EXAMPLE
Tasks
Jobs
Executors
Suspension
Actors
Isolation
Sendable values
Structured concurrency
Cancellation
rather than making the thread itself the centre of every architectural decision.
That is a major step forward.
20 · TUTORIAL
Follow the Whole Journey
We can now follow one piece of code through the entire mental model we have built.
We begin in Xcode:
CODE EXAMPLE
counter += 1
Our application is compiled.
EXECUTION DIAGRAM
Swift
↓
Compiler
↓
Machine Code
The user launches the application.
EXECUTION DIAGRAM
iOS
↓
creates / manages
↓
APP PROCESS
The process contains execution threads.
EXECUTION DIAGRAM
APP PROCESS
│
├── Main Thread
├── Thread 2
└── Thread 3
The operating system schedules a runnable thread onto a processor core.
EXECUTION DIAGRAM
Thread
↓
OS Scheduler
↓
CPU Core
The processor executes machine instructions.
EXECUTION DIAGRAM
LOAD
↓
ADD
↓
STORE
And the resulting state is reflected in the application's memory.
CODE EXAMPLE
counter = 42
That is the journey.
Swift source code at the top.
Machine execution at the bottom.
Processes, threads, operating-system scheduling and processor cores connecting the two.
21 · TUTORIAL
Why This Matters to an iOS Developer
It is very easy to spend years building iOS applications without ever needing to construct this entire picture.
The frameworks deliberately protect us from much of it.
That is a good thing.
But concurrency is one of those subjects where understanding one layer underneath our everyday Swift code pays enormous dividends.
Suddenly a race condition is not just “some weird threading bug.”
A lost update is not mysterious.
The main thread is not a magical UIKit rule.
A context switch is not an abstract operating-system phrase.
And a Swift Task no longer needs to be incorrectly imagined as another name for a thread.
Each concept has somewhere to live in the architecture.
22 · TUTORIAL
What to Remember
💡 What to Remember
The CPU does not execute Swift source code. Swift is compiled into lower-level machine instructions that the processor can execute.
A system thread provides an execution context through which instructions belonging to a process can run.
The operating system schedules runnable threads onto available processor cores.
The processor executes machine instructions and uses architectural state such as registers while performing that work.
One line of Swift can require multiple lower-level operations. You should therefore never assume that one source-code statement is automatically one indivisible or atomic processor operation.
This becomes particularly important when several execution contexts can access the same mutable state.
The process provides the application's execution environment and virtual address space. Threads operate within that process, and processor cores execute the machine instructions associated with scheduled thread execution.
Multiple processor cores introduce the possibility of genuine parallel execution, but they do not remove the need to coordinate shared mutable state.
Swift Concurrency does not replace the processor, the operating system or system threads.
It gives us a higher-level programming and runtime model for structuring asynchronous and concurrent work above those foundations.
23 · TUTORIAL
Your Next Move
We now have a surprisingly complete picture of what happens underneath an iOS application.
Our app runs inside a process.
The process contains threads.
The operating system schedules runnable threads.
The processor executes machine instructions associated with that execution.
But there is another question that naturally follows from this model.
If threads are the execution contexts that ultimately reach the processor, how many of them can an iOS application actually have?
Can we create ten?
One hundred?
One thousand?
Is there a fixed iOS limit?
And if creating more threads gives us more opportunities for concurrent work, why doesn't every application simply create enormous numbers of them?
Answering those questions takes us directly into thread resources, GCD's managed execution model and one of the reasons Swift Concurrency's cooperative approach matters so much.
That is the next article:
How Many Threads Can Each iOS App Create?
