top of page

01 · INTRODUCTION

What Plugs Into the Processor: a Process or the Thread?

The thread.

When the operating system schedules work onto a CPU core, it is a thread's execution context that runs. The process does not get inserted into the processor as one giant executable object.

The process provides the environment in which the application exists: its virtual address space, memory and other resources. Inside that process are one or more threads. Those threads provide the execution contexts through which the application's machine instructions can actually run on processor cores.

This distinction is extraordinarily useful because developers commonly hear phrases such as “the processor is running my app” or “the CPU is executing the process.” Those descriptions are convenient at a high level, but when we zoom into the machinery underneath concurrency, we need a more precise picture.

💡 The Answer

A thread is scheduled onto a processor core.

The process provides the surrounding execution environment and memory belonging to the application.

02 · TUTORIAL

Start With the Diagram We Already Know

Our iOS application runs inside a process.

EXECUTION DIAGRAM

┌─────────────────────────────────────────┐
│              MY APP PROCESS             │
│                                         │
│          Application Memory             │
│                                         │
│          Main Thread                    │
│          Thread 2                       │
│          Thread 3                       │
│                                         │
└─────────────────────────────────────────┘

The process is our application's operating-system container.

It gives the running application an environment in which its code and data can exist. It has a virtual address space and owns resources associated with the running program.

But the process itself is not the stream of execution.

The threads are.

So when we add the processor to our diagram, we should not draw this:

EXECUTION DIAGRAM

┌───────────────────────────────┐
│         APP PROCESS           │
│                               │
│   Memory                      │
│   Main Thread                 │
│   Thread 2                    │
│   Thread 3                    │
│                               │
└───────────────┬───────────────┘
                │
                ▼
             CPU CORE

That diagram can accidentally suggest that the entire process is fed into the processor.

A better mental model is:

EXECUTION DIAGRAM

┌─────────────────────────────────────────┐
│              MY APP PROCESS             │
│                                         │
│          Application Memory             │
│                                         │
│          Main Thread ─────────────┐      │
│          Thread 2                 │      │
│          Thread 3                 │      │
│                                   │      │
└───────────────────────────────────┼──────┘
                                    │
                                    ▼
                               OS Scheduler
                                    │
                                    ▼
                                CPU Core

One runnable thread receives an opportunity to execute.

03 · TUTORIAL

The Process and the Thread Have Different Jobs

The easiest way to remember the distinction is to give each concept a responsibility.

EXECUTION DIAGRAM

PROCESS

Provides the environment
for the running application

        │
        │ contains
        ▼

THREADS

Provide execution contexts
within that process

        │
        │ scheduled onto
        ▼

CPU CORES

Execute machine instructions

The process answers a question similar to:

Which running program does this execution belong to, and what memory and resources belong to it?

The thread answers a different question:

Where is this particular stream of execution currently up to?

And the processor answers the final question:

What machine instruction do I execute now?

Once those responsibilities are separated, the architecture becomes much easier to understand.

04 · TUTORIAL

A Process Can Exist With Several Streams of Execution

If the process itself were the thing that represented one stream of execution, we would immediately have a problem when an application became multithreaded.

Imagine our process contains three threads:

CODE EXAMPLE

MY APP PROCESS

Main Thread
Thread 2
Thread 3

Those three threads can be at completely different places in the application's machine code.

The main thread might currently be handling a user interaction.

Thread 2 might be processing an image.

Thread 3 might be executing work submitted by another subsystem.

One process therefore supports multiple independent execution contexts.

This is precisely why the distinction between a process and a thread exists.

05 · TUTORIAL

Each Thread Needs to Know Where It Is

In our previous articles we discussed the execution state associated with a thread.

Imagine the application's compiled machine code contains a sequence of instructions:

CODE EXAMPLE

Address 1000    Instruction A
Address 1004    Instruction B
Address 1008    Instruction C
Address 1012    Instruction D
Address 1016    Instruction E
Address 1020    Instruction F

Thread 1 might currently be executing around:

CODE EXAMPLE

Address 1012

while Thread 2 might be executing completely different instructions belonging to the same process.

EXECUTION DIAGRAM

THREAD 1

Current execution point
        │
        ▼
Address 1012


THREAD 2

Current execution point
        │
        ▼
Address 8920

Both execution contexts belong to the same application process.

But they are not at the same point in execution.

This is one of the reasons each thread requires its own execution state.

06 · TUTORIAL

The CPU Does Not Need to Understand Your Process as an App

This is where it helps to think one layer lower than Swift.

The processor does not understand that it is currently executing:

CODE EXAMPLE

RocketLaunchApp

It does not know that your application contains a profile screen, a network manager, a settings screen or a beautiful SwiftUI interface.

It does not understand your product.

It executes machine instructions.

At the hardware level, we are dealing with operations such as loading values, storing values, performing arithmetic, comparing values and branching through instructions.

Our higher-level software abstractions are enormously useful to us as developers, but eventually they have to become work that the processor architecture can execute.

07 · TUTORIAL

Follow One Thread All the Way Down

Imagine the user taps a button.

Our Swift code eventually causes some work to happen:

CODE EXAMPLE

func launchRocket() {
    launchCount += 1
}

At the level where we write software, we think about:

EXECUTION DIAGRAM

launchRocket()
       │
       ▼
launchCount += 1

But our application has already been compiled.

So when the program is actually running, processor execution ultimately concerns machine instructions.

EXECUTION DIAGRAM

Swift Source
     │
     ▼
Compilation
     │
     ▼
Machine Code
     │
     ▼
Running Process
     │
     ▼
Thread Execution Context
     │
     ▼
OS Scheduling
     │
     ▼
CPU Core
     │
     ▼
Machine Instructions Execute

This is the chain we want to be able to draw without looking it up.

08 · TUTORIAL

So What Does the OS Scheduler Actually Schedule?

This question now becomes much easier to answer.

At the CPU scheduling level, the operating system schedules runnable threads onto available processor cores.

Imagine three applications are running.

CODE EXAMPLE

PHOTO APP PROCESS

Main Thread
Thread 2


MESSAGES APP PROCESS

Main Thread
Thread 2
Thread 3


OUR APP PROCESS

Main Thread
Thread 2

There are three processes, but there are seven threads in our simplified example.

Some of those threads may be waiting.

Some may be blocked.

Some may be runnable.

The scheduler's job is concerned with assigning runnable execution contexts to available CPU resources according to the operating system's scheduling policies.

So our simplified system-level picture becomes:

EXECUTION DIAGRAM

PROCESS A
  ├── Thread A1 ─────┐
  └── Thread A2      │
                     │
PROCESS B            │
  ├── Thread B1 ─────┤
  ├── Thread B2      ├──▶ OS SCHEDULER
  └── Thread B3      │         │
                     │         ▼
PROCESS C            │      CPU CORES
  ├── Thread C1 ─────┤
  └── Thread C2 ─────┘

This diagram gives us a much more accurate understanding of operating-system multitasking.

09 · TUTORIAL

Then Why Do We Say the OS Switches Between Processes?

Because at a higher level, that description is useful.

If the CPU was executing a thread belonging to App A and then begins executing a thread belonging to App B, from the user's perspective the system has switched processor time between applications.

It is perfectly reasonable to describe that as switching between processes when explaining multitasking at a broad level.

But when we zoom into scheduling and concurrency, the more precise description is that the system schedules threads belonging to those processes.

So both statements can appear:

CODE EXAMPLE

HIGH-LEVEL DESCRIPTION

The OS switches between processes.


MORE PRECISE EXECUTION DESCRIPTION

The OS schedules runnable threads
belonging to those processes
onto CPU cores.

The second description is the one we need when learning concurrency.

10 · TUTORIAL

What Happens to the Process Memory?

This is where the process becomes essential again.

A thread does not exist as an independent little application with its own completely separate copy of all the process's state.

Threads belonging to the same process operate within the same process virtual address space and can access shared process memory.

Imagine:

EXECUTION DIAGRAM

┌─────────────────────────────────────────┐
│              MY APP PROCESS             │
│                                         │
│          SHARED PROCESS MEMORY          │
│                                         │
│             counter = 10                │
│                ▲    ▲                   │
│                │    │                   │
│         Main Thread  Thread 2           │
│                                         │
└─────────────────────────────────────────┘

Both threads belong to the same process.

Both may be able to reach the same mutable value.

This is extremely useful.

It is also the source of one of the fundamental problems of multithreaded programming.

11 · TUTORIAL

The Threads Share Memory but Have Independent Execution

Read that sentence again because it captures the problem beautifully:

💡 Important Idea

Threads within a process can share access to process memory while maintaining independent execution contexts.

That combination is incredibly powerful.

Thread 1 can make progress.

Thread 2 can make progress.

Both can work with data belonging to the same application.

But now we have the possibility that both reach the same mutable state at overlapping times.

And suddenly:

CODE EXAMPLE

counter += 1

is no longer as innocent as it looks.

12 · TUTORIAL

Return to Our Lost Counter

Suppose:

CODE EXAMPLE

var counter = 10

Thread 1 executes work conceptually resembling:

EXECUTION DIAGRAM

READ counter → 10
ADD 1         → 11
WRITE         → 11

Thread 2 wants to do exactly the same thing.

Because these are independent execution contexts, their operations can overlap or interleave.

EXECUTION DIAGRAM

THREAD 1                     THREAD 2

READ 10
                             READ 10

ADD 1 → 11
                             ADD 1 → 11

WRITE 11
                             WRITE 11

Two increments were requested.

The final value can still be:

CODE EXAMPLE

11

Now look at this bug using our process/thread/processor model.

The process is not the problem.

The processor is not malfunctioning.

The operating system is not randomly corrupting our integer.

We created a program in which multiple execution contexts can access shared mutable state without sufficient synchronisation.

The machine is doing exactly what we allowed it to do.

13 · TUTORIAL

This Is Why the Process Must Stay in Our Diagram

It would therefore be a mistake to learn:

CODE EXAMPLE

The thread plugs into the CPU.

and then throw the process away.

The complete model needs both concepts.

EXECUTION DIAGRAM

┌─────────────────────────────────────────┐
│              APP PROCESS                │
│                                         │
│         Shared Address Space            │
│         Application State               │
│         Code / Resources                │
│                                         │
│         Thread 1 ───────────┐            │
│         Thread 2            │            │
│         Thread 3            │            │
│                             │            │
└─────────────────────────────┼────────────┘
                              │
                              ▼
                        OS SCHEDULER
                              │
                              ▼
                          CPU CORE

The process tells us where the execution belongs and gives those threads their shared application environment.

The thread gives us the execution context.

The CPU performs the actual machine instructions.

14 · TUTORIAL

What Happens During a Context Switch?

Suppose the CPU is currently executing Thread 1 from our application.

EXECUTION DIAGRAM

MY APP PROCESS
     │
     ▼
Thread 1
     │
     ▼
CPU CORE

The operating system later decides another runnable thread should execute.

Enough of Thread 1's execution state is preserved so that it can continue correctly later.

The next thread's required execution state is established.

Now the processor can continue using another execution context:

EXECUTION DIAGRAM

ANOTHER PROCESS
      │
      ▼
Thread 4
      │
      ▼
CPU CORE

If the newly scheduled thread belongs to a different process, the execution environment associated with that process must also be respected, including the appropriate virtual-memory context.

This is one reason switching execution has a cost.

The CPU is not simply changing the name written above the current instruction.

Execution context matters.

15 · TUTORIAL

Now Add Multiple Processor Cores

Our single-core model has been deliberately useful because it allows us to see concurrency without parallelism.

Now imagine four cores.

EXECUTION DIAGRAM

MY APP PROCESS

Main Thread ─────────────▶ Core 1
Thread 2 ────────────────▶ Core 2
Thread 3 ────────────────▶ Core 3

ANOTHER APP PROCESS

Main Thread ─────────────▶ Core 4

Several threads can now genuinely execute machine instructions at the same time.

Notice what still has not happened.

We did not connect an entire process to Core 1.

We connected a runnable thread belonging to that process.

Another thread from the same process can simultaneously execute on another available core.

This is parallelism.

16 · TUTORIAL

A Process Can Therefore Use More Than One Core at Once

This follows naturally from the model.

If a process contains multiple runnable threads, the operating system can potentially schedule different threads belonging to that same process onto different processor cores.

EXECUTION DIAGRAM

┌──────────────────────────────────────┐
│            MY APP PROCESS            │
│                                      │
│   Thread 1 ─────────────────▶ Core 1 │
│   Thread 2 ─────────────────▶ Core 2 │
│   Thread 3 ─────────────────▶ Core 3 │
│                                      │
└──────────────────────────────────────┘

This is another reason the phrase:

CODE EXAMPLE

The process runs on the processor.

is too coarse for serious concurrency reasoning.

Which part of the process?

Which thread?

Which core?

There may be several execution contexts belonging to the same process running simultaneously.

17 · TUTORIAL

Now Think About the Main Thread Again

In the previous article, we learned that our application's initial execution begins on the main thread.

We can now place that thread correctly in the complete architecture:

EXECUTION DIAGRAM

┌─────────────────────────────────────────┐
│              MY APP PROCESS             │
│                                         │
│          Application Memory             │
│                                         │
│          MAIN THREAD ─────────────┐      │
│                                  │      │
└──────────────────────────────────┼──────┘
                                   │
                                   ▼
                              OS Scheduler
                                   │
                                   ▼
                               CPU Core
                                   │
                                   ▼
                         Machine Instructions

When we say:

CODE EXAMPLE

Don't block the main thread.

we can now translate it into something much more concrete.

Do not perform long-running synchronous work on the execution context responsible for important main-thread work, because while that thread is occupied executing those instructions, other work requiring that same execution context cannot make timely progress.

The phrase is no longer mysterious.

18 · TUTORIAL

Where Does Grand Central Dispatch Fit?

GCD sits above these underlying execution resources.

When we write:

CODE EXAMPLE

DispatchQueue.global().async {
    processImage()
}

we are not plugging a dispatch queue into the processor.

A queue is not something a CPU core executes.

We are submitting work to GCD.

GCD coordinates the execution of that work using underlying worker threads.

Eventually:

EXECUTION DIAGRAM

DISPATCHED WORK
       │
       ▼
GCD
       │
       ▼
Worker Thread
       │
       ▼
OS Scheduling
       │
       ▼
CPU Core

Again, the thread is the execution context that ultimately reaches the processor.

19 · TUTORIAL

And Where Does Swift Concurrency Fit?

The same rule still applies.

When we create:

CODE EXAMPLE

Task {
    await loadProfile()
}

the Task itself is not plugged into a CPU core.

A Task is a higher-level representation of asynchronous work.

Eligible synchronous portions of Swift concurrency work are arranged through executors and eventually execute using underlying system threads.

A useful simplified picture is:

EXECUTION DIAGRAM

SWIFT TASK
     │
     ▼
Runnable Job
     │
     ▼
Executor
     │
     ▼
System Thread
     │
     ▼
OS Scheduler
     │
     ▼
CPU Core
     │
     ▼
Machine Instructions

The hardware has not suddenly learned what a Swift Task is.

The processor still executes machine instructions through an execution context supplied by a system thread.

Swift Concurrency gives us a much better abstraction for deciding how application work should be organised above that machinery.

20 · TUTORIAL

This Is Why “Task Is Not a Thread” Matters

Developers are frequently told:

CODE EXAMPLE

Task is not a thread.

Without understanding the underlying architecture, that sentence becomes another fact to memorise.

Now we can actually explain it.

A thread is an operating-system execution context that can be scheduled onto a processor core.

A Swift Task represents asynchronous work managed by the Swift concurrency system.

The Task may suspend.

It may later become eligible to continue.

Its work ultimately requires a thread when machine instructions actually need to execute, but the Task does not need to permanently own one particular thread for its entire lifetime.

That separation is one of the major advantages of the modern concurrency model.

21 · TUTORIAL

Three Layers — Three Different Responsibilities

If you remember only one diagram from this article, make it this one:

EXECUTION DIAGRAM

PROCESS

│  "Where does this running
│   application and memory belong?"



THREAD

│  "What execution context
│   is making progress?"



CPU CORE

│  "Which machine instruction
│   do I execute now?"



MACHINE EXECUTION

Those are different layers.

Do not collapse them into one idea.

The process is not a thread.

The thread is not a processor.

A dispatch queue is not a thread.

A Swift Task is not a thread.

And none of those high-level abstractions changes the fundamental fact that processor cores ultimately execute machine instructions.

22 · TUTORIAL

Why This Matters to an iOS Developer

You may wonder whether knowing precisely what gets scheduled onto a CPU core actually changes the Swift you write tomorrow morning.

It does, because it removes ambiguity from almost every concurrency discussion that follows.

When somebody says:

CODE EXAMPLE

Move this onto another thread.

you understand what a thread actually represents.

When somebody says:

CODE EXAMPLE

Dispatch this to a background queue.

you understand that the queue is an abstraction for organising work and not the physical execution context itself.

When somebody says:

CODE EXAMPLE

Task doesn't create a thread.

you understand why it does not need to.

When somebody says:

CODE EXAMPLE

These two threads are racing
over the same value.

you can picture two independent execution contexts belonging to the same process, potentially reaching the same shared memory while their instructions are scheduled onto available CPU cores.

And when somebody says:

CODE EXAMPLE

Swift Concurrency is cooperative.

you now have the lower-level architecture onto which that new model must eventually map.

This is what understanding gives us that memorising terminology does not.

23 · TUTORIAL

What to Remember

💡 What to Remember

At the CPU scheduling level, it is a runnable thread's execution context that is scheduled onto a processor core — not an entire process as one indivisible unit.

The process provides the running application's environment, including its virtual address space and resources.

A process can contain multiple threads, and each thread represents an independent execution context within that process.

Threads belonging to the same process can access shared process memory, which is one reason concurrent access to mutable state requires coordination.

The operating system schedules runnable threads onto available processor cores.

On a multicore processor, several threads belonging to the same process may execute in parallel on different cores.

Grand Central Dispatch queues are not threads. GCD uses underlying worker threads to execute submitted work.

Swift Tasks are not threads either. Swift Concurrency manages asynchronous work at a higher level, while eligible work ultimately executes using system threads scheduled by the operating system.

The complete foundational model is:

EXECUTION DIAGRAM

Process
   ↓
Threads
   ↓
OS Scheduling
   ↓
CPU Cores
   ↓
Machine Instructions

24 · TUTORIAL

Your Next Move

We have now established exactly what reaches the processor.

The application lives inside a process.

The process contains threads.

Runnable threads are scheduled onto processor cores.

The processor executes machine instructions.

But our system-level diagram has introduced another question.

Imagine three applications:

EXECUTION DIAGRAM

APP A PROCESS
  ├── Main Thread
  └── Thread 2

APP B PROCESS
  ├── Main Thread
  ├── Thread 2
  └── Thread 3

APP C PROCESS
  ├── Main Thread
  └── Thread 2

What happens to all of those threads?

Does iOS take every thread from every running application and place them into one enormous global collection?

Or do the threads remain conceptually and structurally associated with the process that owns them while the operating system schedules runnable threads across the entire system?

Understanding that distinction will complete our picture of how processes, threads and system-wide scheduling fit together.

That is the next article:

Are Threads Grouped Together Globally in the OS or Are They Confined Inside Each App?

bottom of page