top of page

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?

bottom of page