01 · INTRODUCTION
Are Threads Grouped Together Globally in the OS or Are They Confined Inside Each App?
Threads belong to processes. Your app's threads are associated with your app's process, another app's threads belong to that app's process, and the operating system schedules runnable threads from across the system onto the available processor cores.
This distinction is important because there are really two different views happening at the same time.
From the application's point of view, threads are part of the process that owns them. They execute using that process's address space and resources.
From the operating system's point of view, however, runnable threads from many different processes may all be competing for processor time.
So the answer is not that every thread becomes detached from its app and thrown into one anonymous global pool. Threads remain associated with their processes, while the OS has a system-wide scheduling responsibility over runnable threads.
💡 The Answer
Threads belong to individual processes, but the operating system schedules runnable threads from across all of those processes onto the available CPU cores.
02 · TUTORIAL
Start With One App
Take the process model we have been using throughout this series.
EXECUTION DIAGRAM
┌─────────────────────────────────────────┐
│ MY APP PROCESS │
│ │
│ Application Memory │
│ │
│ Main Thread │
│ Thread 2 │
│ Thread 3 │
│ │
└─────────────────────────────────────────┘
The threads are not outside the process.
They belong to it.
They execute within the context of that process and use its virtual address space.
This means Thread 2 from our application is fundamentally connected to our application's process. It is not simply a generic operating-system thread that can suddenly begin executing instructions belonging to another unrelated application's memory space.
03 · TUTORIAL
Now Add Another App
Imagine another application is also running.
EXECUTION DIAGRAM
┌─────────────────────────────────────────┐
│ APP A PROCESS │
│ │
│ Main Thread │
│ Thread 2 │
│ Thread 3 │
│ │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ APP B PROCESS │
│ │
│ Main Thread │
│ Thread 2 │
│ │
└─────────────────────────────────────────┘
App A has its threads.
App B has its threads.
The operating system knows which process each thread belongs to.
That ownership matters because each process has its own virtual address space and resources. A thread executes as part of that process's environment.
04 · TUTORIAL
Threads Are Not Shared Between Unrelated App Processes
It would be a mistake to imagine that iOS maintains one enormous collection of anonymous threads and simply hands those threads to applications without any process association.
This model would be misleading:
CODE EXAMPLE
GLOBAL THREAD COLLECTION
Thread 1
Thread 2
Thread 3
Thread 4
Thread 5
Thread 6
Apps borrow random threads
with no process ownership
Threads are execution contexts belonging to processes.
A more useful model is:
EXECUTION DIAGRAM
APP A PROCESS
├── Thread A1
├── Thread A2
└── Thread A3
APP B PROCESS
├── Thread B1
└── Thread B2
APP C PROCESS
├── Thread C1
├── Thread C2
└── Thread C3
Each thread remains associated with the process that owns it.
05 · TUTORIAL
So Where Does the Global OS View Appear?
The global view appears when the operating system needs to decide which runnable thread should receive processor time.
Imagine three app processes:
EXECUTION DIAGRAM
APP A
├── A1
├── A2
└── A3
APP B
├── B1
└── B2
APP C
├── C1
├── C2
└── C3
Some of these threads may currently be waiting.
Others may be runnable.
The operating system has to coordinate runnable execution across the entire machine, not just inside one application.
Our simplified scheduling picture therefore becomes:
EXECUTION DIAGRAM
APP A PROCESS
├── Thread A1 ──────────┐
├── Thread A2 │
└── Thread A3 ──────────┤
│
APP B PROCESS │
├── Thread B1 ──────────┤
└── Thread B2 ├────▶ OS SCHEDULER
│ │
APP C PROCESS │ ▼
├── Thread C1 ──────────┤ CPU CORES
├── Thread C2 │
└── Thread C3 ──────────┘
The ownership remains process-local.
The scheduling responsibility is system-wide.
06 · TUTORIAL
This Is How Multitasking and Multithreading Meet
Multitasking describes the operating system managing work belonging to multiple running programs.
Multithreading describes a process containing multiple execution threads.
Put those together and we get a system containing several processes, each of which may contain several threads.
EXECUTION DIAGRAM
OPERATING SYSTEM
Process A
├── Thread A1
├── Thread A2
└── Thread A3
Process B
├── Thread B1
└── Thread B2
Process C
├── Thread C1
├── Thread C2
└── Thread C3
│
▼
System-wide scheduling
│
▼
CPU cores
This is one of the most useful diagrams to hold in your head when learning concurrency.
Processes organise running applications.
Threads provide execution contexts inside those processes.
The operating system schedules runnable threads across the machine.
07 · TUTORIAL
The Process Boundary Still Matters
If scheduling is system-wide, you might wonder whether the process boundary becomes irrelevant once a thread reaches the processor.
It does not.
The thread still executes as part of its owning process.
That means the process's virtual address space and other execution context still matter.
Imagine two applications both contain a variable called:
CODE EXAMPLE
var counter = 0
Those variables do not automatically refer to the same memory simply because they have the same name.
CODE EXAMPLE
APP A PROCESS
counter = 0
APP B PROCESS
counter = 0
They exist in different process address spaces.
A thread belonging to App A executes within App A's environment.
A thread belonging to App B executes within App B's environment.
The OS can schedule both threads, but their process ownership remains fundamental.
08 · TUTORIAL
Threads Within the Same Process Are Different
Now compare that with two threads inside the same process.
EXECUTION DIAGRAM
┌─────────────────────────────────────────┐
│ MY APP PROCESS │
│ │
│ counter = 0 │
│ ▲ ▲ │
│ │ │ │
│ Main Thread Thread 2 │
│ │
└─────────────────────────────────────────┘
These threads operate within the same process address space.
That means they may be able to access the same application state.
This is precisely why multithreading inside a single process can create data races and race conditions.
The threads have independent execution contexts but shared access to process memory.
💡 Important Idea
Threads from different processes are separated by process boundaries. Threads inside the same process can share access to that process's memory.
09 · TUTORIAL
This Is Why Processes Are Useful
Processes give the operating system a strong boundary between running programs.
Imagine if every application's threads could simply access one giant shared memory space.
One application's bug could overwrite another application's data.
A stray pointer in one program could interfere directly with another completely unrelated program.
The system would be dramatically harder to make reliable and secure.
Instead, process isolation gives each application its own virtual address space and controlled access to operating-system resources.
Threads inside that process can cooperate with one another using the process's memory, while unrelated processes remain separated unless they communicate through explicit operating-system mechanisms.
10 · TUTORIAL
The CPU Can Switch Between Threads From Different Processes
Return to our simplified single-core processor.
The operating system might first schedule a thread from App A:
EXECUTION DIAGRAM
CPU CORE
App A
Thread A1
████████
Then it may schedule a thread from App B:
EXECUTION DIAGRAM
CPU CORE
App A / Thread A1
████████
App B / Thread B1
████████
Then perhaps another thread from App A:
EXECUTION DIAGRAM
TIME ─────────────────────────────────────▶
CPU CORE
A1 B1 A2 C1
████████ ████████ ████████ ████████
The processor core is therefore not permanently attached to one application process.
Processor time is shared across the system according to the operating system's scheduling decisions.
Each scheduled thread brings its own execution context and remains associated with its owning process.
11 · TUTORIAL
What Has to Change When the OS Switches Processes?
When execution moves between threads belonging to different processes, more than the thread's current instruction position matters.
The new thread belongs to another process with another virtual address space and another execution environment.
Conceptually:
EXECUTION DIAGRAM
THREAD A1
belongs to
PROCESS A
│
▼
CPU executes A1
CONTEXT SWITCH
THREAD B1
belongs to
PROCESS B
│
▼
CPU executes B1
The operating system and processor architecture work together to ensure execution occurs using the correct process memory context.
We do not need to study virtual-memory page tables or kernel internals in depth to understand the central idea.
Changing threads can also mean changing the process environment in which those instructions execute.
12 · TUTORIAL
The OS Is Scheduling the Whole Machine
This also explains why our app cannot assume that it owns the processor.
We often think about performance entirely from inside our own process:
CODE EXAMPLE
My Main Thread
My Background Work
My Networking
My Database
My Tasks
But the operating system has a much larger problem.
It must coordinate processor resources across:
CODE EXAMPLE
Our App
Other Apps
System Services
Framework Work
Networking
Media Processing
Background Activity
Kernel Work
and other runnable execution
Your process is one participant in a much larger system.
This is why a thread does not simply “own” a CPU core until its entire feature finishes.
The operating system is responsible for the whole device.
13 · TUTORIAL
Priorities Can Influence Scheduling
Not every runnable thread necessarily has identical scheduling importance.
Operating systems use scheduling policies and priorities to help decide how processor time should be distributed.
This does not mean application developers can reliably predict an exact execution timeline.
It means the scheduler has more information than simply:
CODE EXAMPLE
Thread A exists.
Thread B exists.
Choose randomly.
The real system accounts for priorities, system policies, resource availability and other implementation details.
For our concurrency model, however, the crucial rule remains:
The operating system chooses which runnable threads receive processor time.
14 · TUTORIAL
This Explains Why Timing Between Threads Is Unreliable
Imagine App A contains two independently executing pieces of work.
CODE EXAMPLE
DispatchQueue.global().async {
print("First")
}
DispatchQueue.global().async {
print("Second")
}
The operating system is simultaneously scheduling work belonging to many different processes and system components.
Our two pieces of application work exist inside that much larger scheduling environment.
This is another reason we cannot look at two independently concurrent operations and infer their completion order merely from the order in which the Swift source appears.
If ordering is important, our program must express that requirement through an appropriate concurrency mechanism.
We should not rely on accidental scheduling behaviour.
15 · TUTORIAL
What About GCD Worker Threads?
Grand Central Dispatch can manage worker threads used to execute work submitted to dispatch queues.
Those worker threads still belong to the process in which that work is executing.
GCD does not create one universal set of application-memory-sharing threads that freely cross process boundaries.
Think of it like this:
EXECUTION DIAGRAM
APP A PROCESS
GCD Work
│
▼
App A Worker Threads
│
▼
OS Scheduler
APP B PROCESS
GCD Work
│
▼
App B Worker Threads
│
▼
OS Scheduler
The operating system can then schedule runnable threads from both processes onto available processor cores.
Again, local ownership and global scheduling coexist.
16 · TUTORIAL
What About Swift Concurrency?
The same process boundary continues to exist when we move into Swift Concurrency.
Suppose our app creates:
CODE EXAMPLE
Task {
await loadLaunches()
}
That Task belongs to the running Swift program inside our application process.
The Task is not globally mixed with another application's Swift state.
Swift Concurrency manages asynchronous work inside our program, and eligible jobs ultimately execute using system execution resources belonging to that process.
The operating system then schedules the underlying runnable threads along with other runnable threads across the device.
A useful layered model is:
EXECUTION DIAGRAM
OUR APP PROCESS
Swift Tasks
│
▼
Swift Jobs
│
▼
Executors
│
▼
System Threads
│
│
└──────────────┐
│
OTHER PROCESSES │
│
System Threads ─────┼──▶ OS SCHEDULER
│ │
│ ▼
│ CPU CORES
│
System Threads ─────┘
This diagram connects almost everything we have learned so far.
17 · TUTORIAL
Swift Tasks Are Not Globally Shared Between Apps Either
It is worth extending the distinction one layer upward.
When your application creates a Swift Task, that Task is part of your running program.
Another application has its own process and its own concurrency runtime state.
So we should not imagine one giant device-wide Swift Task collection such as:
CODE EXAMPLE
GLOBAL IOS TASK POOL
Your Task
Instagram Task
Messages Task
Photos Task
System Task
Your Task
Another App Task
Swift's application-level concurrency abstractions live inside their respective running programs.
The operating system's scheduler operates beneath that abstraction at the level of system execution resources.
18 · TUTORIAL
There Are Therefore Two Schedulers to Keep Separate
This is an important insight as we move deeper into Swift Concurrency.
There is operating-system scheduling.
EXECUTION DIAGRAM
RUNNABLE SYSTEM THREADS
│
▼
OS SCHEDULER
│
▼
CPU CORES
And there is Swift Concurrency's higher-level scheduling of eligible jobs through executors.
EXECUTION DIAGRAM
SWIFT TASKS
│
▼
ELIGIBLE JOBS
│
▼
EXECUTORS
│
▼
SYSTEM EXECUTION RESOURCES
These are not the same scheduler.
Swift Concurrency decides how eligible Swift concurrency work is arranged within the rules of its executor model.
The operating system ultimately decides when runnable system threads receive processor time.
Keeping those two layers separate prevents enormous confusion.
19 · TUTORIAL
This Is Why Cooperative and Preemptive Scheduling Can Coexist
At first, developers sometimes hear two apparently contradictory statements.
CODE EXAMPLE
The OS uses preemptive thread scheduling.
and:
CODE EXAMPLE
Swift Concurrency uses cooperative scheduling.
Both can be true because they describe different layers.
The OS can preempt system-thread execution according to its scheduler.
Swift's task model is cooperative in the way task jobs and suspension points participate in the concurrency runtime.
A simplified stack is:
EXECUTION DIAGRAM
Swift Task
│
▼
Swift Job
│
▼
Executor
│
▼
System Thread
│
▼
Preemptive OS Scheduling
│
▼
CPU Core
Once you understand that hierarchy, the two scheduling models stop sounding contradictory.
20 · TUTORIAL
Threads Are Confined by Ownership, Not by Processor Access
There is a useful sentence that summarises the entire article.
💡 Important Idea
A thread belongs to its process, but processor access is scheduled globally by the operating system.
That is the relationship.
Process ownership tells us which execution environment and memory belong to the thread.
The OS scheduler decides when runnable threads across those processes get opportunities to execute.
The processor cores are shared hardware resources underneath all of them.
21 · TUTORIAL
Draw the Complete Operating-System Model
At this point we can finally draw the larger architecture we have been building article by article.
EXECUTION DIAGRAM
┌─────────────────────────────────────┐
│ APP A PROCESS │
│ │
│ Memory │
│ Main Thread ─────────────┐ │
│ Thread A2 │ │
│ Thread A3 ──────────────┤ │
└──────────────────────────────┼──────┘
│
┌──────────────────────────────┼──────┐
│ APP B PROCESS │ │
│ │ │
│ Memory │ │
│ Main Thread ────────────┤ │
│ Thread B2 │ │
└──────────────────────────────┼──────┘
│
┌──────────────────────────────┼──────┐
│ APP C PROCESS │ │
│ │ │
│ Memory │ │
│ Main Thread ────────────┤ │
│ Thread C2 ──────────────┤ │
│ │ │
└──────────────────────────────┼──────┘
│
▼
OS SCHEDULER
│
┌─────────────┼─────────────┐
▼ ▼ ▼
CPU CORE CPU CORE CPU CORE
1 2 3
This is the model worth learning.
Apps are separated into processes.
Processes contain their own threads.
Runnable threads from across the system become candidates for processor time.
The operating system schedules them onto the available cores.
22 · TUTORIAL
Why This Matters to an iOS Developer
This may initially sound like operating-systems theory rather than everyday Swift development.
But look at how many common iOS questions it answers.
Why can two threads in my app race over one property?
Because they belong to the same process and can reach shared process memory.
Why can't another random app simply mutate that property?
Because the applications exist in separate process address spaces.
Why can my app lose CPU time while it is doing work?
Because the operating system schedules processor resources across the entire system.
Why doesn't creating another thread give me another CPU?
Because threads are execution contexts while processor cores are finite hardware resources.
Why can Swift Concurrency be cooperative while the OS is preemptive?
Because those scheduling models exist at different layers.
Once again, a collection of seemingly disconnected concurrency facts turns into one architecture.
23 · TUTORIAL
What to Remember
💡 What to Remember
Threads belong to processes.
Your application's threads execute as part of your application's process and use that process's virtual address space and resources.
Another application has a different process and its own threads.
Threads belonging to different processes remain separated by the operating system's process boundaries.
Threads within the same process can access shared process memory, which is why concurrent mutable state can create data races and race conditions.
The operating system has a system-wide responsibility for scheduling runnable threads from many processes onto the available processor cores.
A thread therefore belongs locally to a process while competing globally for processor time.
Grand Central Dispatch worker threads still execute within the process whose work they are serving.
Swift Tasks and executors live at a higher abstraction level inside the running program. Their eligible work ultimately executes through system threads.
Swift cooperative scheduling and OS preemptive scheduling can coexist because they operate at different layers of the execution stack.
The complete model is:
EXECUTION DIAGRAM
App
↓
Process
↓
Threads
↓
System-wide OS Scheduling
↓
CPU Cores
↓
Machine Instructions
24 · TUTORIAL
Your Next Move
We have now completed the basic operating-system model underneath concurrency.
We know what a process is.
We know where threads live.
We know that an iOS application starts with a main thread.
We know how the operating system switches between runnable threads.
We know that threads — rather than whole processes — provide the execution contexts scheduled onto processor cores.
And we now know that threads remain associated with their own processes even though the operating system schedules processor time across threads from the entire system.
That gives us enough information to ask a much more interesting Swift Concurrency question.
If the operating system already knows how to take thousands of runnable threads from across many processes and schedule them onto a much smaller number of processor cores, why did Swift need another concurrency runtime at all?
Why not simply keep creating threads?
Why introduce Tasks?
Why introduce suspension?
Why introduce executors?
And what exactly does Swift mean when it says our concurrent work should now be cooperative?
That is where our operating-system foundation begins turning directly into Swift Concurrency.
The next article should be:
Why Does Swift Concurrency Need a Runtime?
