AlgoMaster Logo

What a Thread Is to the Kernel

16 min readUpdated August 7, 2026
Listen to this chapter
Unlock Audio

A backend service may appear to be one running program, yet it can have several activities in progress:

  • One request handler is executing application code.
  • Another is waiting for a database response.
  • A background worker is compressing data.
  • A runtime helper is performing housekeeping.

The kernel cannot schedule “the service” as one indivisible object if these activities must run and wait independently. It needs a separate execution context for each one.

That execution context is a thread.

To the kernel, a thread is an independently schedulable execution context: the state required to run one sequence of instructions and resume it later.

This definition is more precise than saying that a thread is a “lightweight process.” The word lightweight describes a possible cost difference, but it does not explain what the object is. A thread is the unit that the scheduler can place on a logical CPU, preempt, block, wake, and later continue.

Threads as Instruction Streams

A CPU executes instructions according to state held in its registers. Two registers are especially important:

  • The instruction pointer identifies the next instruction to execute.
  • The stack pointer identifies the current position in the thread's stack.

General-purpose registers hold operands, addresses, return values, and intermediate results. Status registers hold execution flags and other control state.

Together, this state describes one point in one computation:

Thread A holdsWhich points to
Instruction pointerThe next instruction in handle_request()
Stack pointerThe current call frame
RegistersArguments and intermediate values

Another thread can execute the same program code while being at a completely different point:

RegisterThread AThread B
Instruction pointerparse_request()compress_response()
Stack pointerA's call framesB's call frames
General registersA's valuesB's values

The code itself does not define a thread. Several threads can execute the same function, and one thread can execute many functions over its lifetime. What distinguishes them is their independent execution state.

A thread is also sequential from its own point of view. Its instruction pointer describes one current position. A single thread does not run on two CPUs at once. If an application wants two instruction streams to execute simultaneously on two logical CPUs, it needs at least two runnable threads or equivalent schedulable entities.

Thread Scheduling by the Scheduler

When the kernel chooses what should run on a CPU, it chooses a thread.

Suppose one process contains three threads:

Thread B cannot make progress until its data arrives, so it is not a useful scheduling candidate. The scheduler can choose A or C:

Thread B is not a candidate at all. The scheduler picks among threads that can actually make progress, so a waiting thread costs nothing but the memory that describes it.

If A blocks while reading from a socket, the kernel can run C. If the network data for B arrives, the kernel marks B runnable and considers it for CPU time again.

This is why statements such as “the process is running” are convenient but imprecise. A single-threaded process has only one thread, so the distinction is easy to ignore. In a multithreaded process, its threads can be in different states at the same moment:

There is no single scheduling state that accurately describes the whole process. Scheduling state belongs to each thread.

On a four-CPU machine, four runnable threads from one process could execute in parallel. The kernel may also place threads from four different processes on those CPUs. From the scheduler's perspective, both cases present independently schedulable work.

What the Kernel Must Remember for Each Thread

Only a running thread has its live execution state in a CPU's registers. When the thread stops running, the kernel must preserve enough information to continue it later as though the interruption had not happened.

The kernel therefore maintains several categories of per-thread state.

Identity

Every kernel-scheduled thread needs an identity so the kernel, debuggers, tracing tools, and system calls can refer to that specific execution context.

On Linux, this identity is the thread ID, or TID. Each thread has a distinct TID within its PID namespace.

Saved CPU context

The saved execution context includes the instruction pointer, stack pointer, general-purpose registers, status information, and architecture-specific state needed to resume execution.

Conceptually:

The exact save and restore mechanics are architecture-dependent. The important idea is that a suspended thread is not just “some code that will run again.” It has a precise continuation point.

Scheduling information

The scheduler needs per-thread metadata such as:

  • Whether the thread is running, runnable, or waiting
  • Its scheduling policy and priority-related values
  • Which CPUs it is allowed to run on
  • Runtime statistics used for accounting and scheduling decisions

CPU affinity applies naturally to threads. Two threads in the same process can be allowed to run on different sets of CPUs.

A user-space stack

Each ordinary application thread follows its own chain of function calls, so it needs its own user-space stack and stack pointer.

Threads A and B execute the same shared program code, but each has its own user stack.

The word own describes how the stack is used, not necessarily a hardware-enforced protection boundary. Stacks for threads in the same process normally occupy different regions of the same virtual address space. A faulty pointer in one thread can therefore corrupt another thread's stack.

A kernel stack

When a thread enters the kernel through a system call, exception, or interrupt, the kernel needs a safe stack on which to execute kernel code for that thread. Linux gives each task its own kernel stack.

This separation is necessary because several threads can be inside the kernel at once on different CPUs. A thread can also sleep while partway through a system call. Its kernel call chain must remain intact until that same thread wakes and continues.

The two threads never share a stack on either side of the boundary. Each has its own kernel stack, which is what lets two threads of the same process be inside the kernel at the same time.

The kernel stack is not the same object as the user stack. Application code cannot use it as ordinary memory, and the kernel switches to the appropriate kernel stack when handling that thread in kernel mode.

Other thread-specific state

The kernel also tracks details such as a thread's signal mask, per-thread pending signals, thread-local-storage setup, and bookkeeping needed by tracing and debugging facilities.

Not every attribute associated with a running program is stored separately per thread. A thread also refers to larger resource objects associated with its process. That distinction lets the kernel preserve an independent execution path while several threads use a common application environment.

Execution State vs. Resources

A useful mental model separates execution state from process resources.

Execution state answers:

Where is this instruction stream, and what does it need to continue?

Process resources answer:

What environment and kernel-managed objects can this instruction stream use?

The execution state includes each thread's registers, scheduling state, stack usage, and identity. The common resources include the program's address space and several kernel-managed resource collections.

For this lesson, the important point is the separation itself. A thread does not need an independent copy of every application resource in order to be scheduled independently. The exact sharing boundary between processes and threads is a separate design question.

This model also explains why the kernel can block one thread without automatically making every thread in the process unable to run. If Thread A waits for a socket operation, its execution state becomes waiting. Thread B has a different execution state and may remain runnable while using the same application environment.

Thread-Local Scope of Blocking Calls

Consider two request-handling threads in one server:

If no data is available for client A, the kernel may put Thread A to sleep. Thread A no longer competes for a CPU because it cannot complete the read yet.

Thread B can continue:

A's wait is not wasted CPU time. The moment A blocks, B becomes the thread the CPU runs.

When data arrives, the kernel records that Thread A is eligible to run again. It does not restart the thread from the beginning. A eventually resumes through the system-call path and continues from the instruction after the read call, with the call returning its result.

The general rule is:

A blocking operation changes the schedulability of the thread that performed it.

Some events deliberately affect an entire process, and application-level dependencies can make other threads unable to do useful work. Those cases do not change the kernel's basic scheduling unit: each thread still has its own execution and scheduling state.

The Linux Task Model

Linux uses the word task as a unifying kernel concept for schedulable execution contexts. Each thread is represented by its own task descriptor, commonly discussed through the task_struct data structure.

This can be surprising because user-space terminology draws a strong line between processes and threads, while Linux represents both with tasks.

A Linux task owns:

  • Unique scheduling and execution state
  • A unique thread ID
  • A kernel stack
  • Scheduler metadata
  • References to resource objects

A single-threaded process has one task. A multithreaded process has multiple tasks organized into a thread group.

The task descriptor does not contain every byte of every resource directly. It contains fields and references that connect the task to address-space information, open-file information, credentials, signal state, namespaces, and other kernel objects. Some referenced objects are common to a thread group, while other state belongs to one task.

The practical consequence is simple:

On Linux, adding an ordinary kernel-scheduled thread adds another task the scheduler can run.

It does not create another schedulable subunit inside one opaque process object. The new thread is itself visible to the scheduler.

PID, TID, and Thread Groups on Linux

Linux exposes two related identities that are often both displayed as a “PID.”

A TID identifies one task—that is, one kernel-scheduled thread.

A thread group ID, or TGID, identifies the collection of tasks that user space normally calls a process. The thread-group leader's TID becomes the TGID for the group.

Suppose a process has three threads:

The group takes its ID from the leader's TID, which is why the first thread's number appears in two places.

The initial thread has TID 8400, so its TID and TGID are equal. The other threads have their own TIDs, but all three report the same TGID.

In ordinary Linux user-space output, the TGID is usually labeled PID because it identifies the process as a whole. This gives the following relationship:

CallReturnsAcross threads
getpid()The thread group IDThe same result in every thread
gettid()The calling thread's IDDifferent for each thread

This naming can make diagnostic output confusing. A monitoring tool may aggregate CPU use under the process PID, while a profiler or debugger reports the TID of the particular thread consuming the CPU.

A POSIX pthread_t value is a separate, library-level thread identifier. Programs should treat it as opaque. It is not a portable substitute for the Linux kernel TID.

Loading simulation...

Observing Threads on Linux

Linux exposes the tasks in a process through:

The directory contains one subdirectory per TID. If process 8400 has three threads, its task directory might contain:

Each thread directory exposes information about that particular task. For example, its status file includes Tgid and Pid fields:

Here, Tgid identifies the process, while Pid identifies the individual Linux task. In this per-thread file, the field named Pid is effectively the TID.

The ps command can display the same distinction:

PID shows the common process identity, TID shows each thread identity, and PSR shows the logical CPU on which the thread most recently ran.

For an interactive per-thread view, Linux top supports:

This is useful when a process appears to consume one full CPU. The process-level number alone does not reveal whether one thread is saturated or many threads are sharing the work.

A Small Identity Demonstration

The following Linux program starts two additional threads only so that their kernel identities can be observed. Thread creation mechanics are not the focus here.

Save it as thread-identity.c, then compile and run it:

An example run may print:

The exact numbers change on every run. The relationship does not:

  • All calls to getpid() return the same thread-group identity.
  • Every call to gettid() returns the identity of the calling task.
  • The main thread's TID equals the process PID.
  • /proc/<PID>/task/ contains an entry for each live thread.

During the sleep calls, the worker threads are waiting rather than consuming CPU time. They still exist as kernel tasks; they are simply not runnable until their timers expire.

Thread Lifetime and Process Lifetime

A thread's lifetime begins when the kernel establishes a new execution context and makes it eligible to run. Its lifetime ends when that execution context exits and the kernel completes the required cleanup.

Threads in one process can begin and end at different times. A long-running server might create a set of worker threads during startup, retain them for hours, and shut them down when the service exits.

The kernel must distinguish:

  • Cleanup that belongs to the terminating thread
  • Process resources that must remain because other threads still use them
  • Final process-wide cleanup after the last thread exits

This is another consequence of separating execution state from resources. Ending one instruction stream does not imply that every resource reachable by the remaining threads should disappear.

The user-space runtime or thread library also performs bookkeeping around thread startup and termination. The low-level creation and teardown protocol is more involved than merely allocating a stack, but those mechanics do not change the definition of the resulting kernel object: it is a new schedulable execution context.

Summary

A thread is the kernel's independently schedulable execution context. It represents one instruction stream and carries the identity, saved CPU state, stacks, scheduling metadata, and other information needed to stop and later resume that computation.

The scheduler operates on runnable threads, so threads in one process can be running, waiting, and waking independently. On Linux, each thread is represented as a task with its own TID, while tasks in the same process belong to one thread group identified by a common TGID, usually shown to user space as the PID.

The central mental model is:

A process provides an execution environment; a thread is one schedulable path of execution through that environment.

Quiz

What a Thread Is to the Kernel Quiz

5 quizzes