Home / Alt manpages / proc_pid_task(5)

  • proc_pid_task(5)
  • File format
  • linux

Inspect Linux Threads Through /proc Without Mixing Up Their IDs

By the end of this guide you will be able to map a process ID to its thread IDs, inspect a particular thread, and use /proc/thread-self when a program needs to refer to the thread doing the work. The commands are read-only. Allow about 10 minutes, plus time to reproduce the example inside the process you are investigating.

Before you start

You need a Linux system with the proc filesystem mounted. The examples use the shell's own process, so they need no elevated privileges. Reading another user's process may be restricted by the system's procfs mount options or by other permission checks; do not work around that boundary casually.

The local reference for this guide is Linux man-pages 6.7, specifically proc_pid_task(5) dated 2023-08-15. The paths described here are kernel interfaces, not commands supplied by the manpages package. Details can therefore also depend on the running kernel and how /proc is mounted.

1. Establish the process and thread IDs

A Linux process has a process ID (PID). Each thread also has a thread ID (TID). The process's main thread has the same numeric value for both; other threads have their own TIDs. Start with a PID you can inspect safely:

pid=$$
printf 'shell PID: %s\n' "$pid"
ps -o pid=,tid=,comm= -p "$pid"

Typical output is similar to this, although the command name and numbers will differ:

shell PID: 24831
  24831   24831 bash

Checkpoint

Keep the PID in pid. Every path in the next step is derived from that value, which avoids accidentally inspecting a similarly named process.

2. List the threads belonging to a process

Read /proc/<pid>/task/ to enumerate the process's threads. Each numeric directory is a TID:

printf 'threads for PID %s:\n' "$pid"
ls -1 "/proc/$pid/task"
ps -o pid=,tid=,comm= -p "$pid"

For a single-threaded shell, both commands usually show one directory and one row. A multithreaded process produces one directory per running thread, for example:

threads for PID 24831:
24831
24832
24833
  24831   24831 worker
  24831   24832 worker
  24831   24833 worker

The task directories contain files with the same names as the process directory. Some values are shared: every thread normally sees the same current working directory through its cwd entry. Other values are thread-specific, and some entries exist only below a task directory. Compare a status file when you need to see per-thread state:

tid=$(find "/proc/$pid/task" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort -n | head -n 1)
printf 'selected TID: %s\n' "$tid"
grep -E '^(Name|State|Pid|PPid|Tgid):' "/proc/$pid/task/$tid/status"

There is a small race here: a thread can exit between the directory listing and the read. Treat a missing file or an ENOENT error as a process changing while you observed it, not as proof that the path syntax was wrong.

3. Inspect a thread through /proc/<tid>

Linux also exposes a numeric /proc/<tid>/ path for each running thread that is not a thread-group leader. In other words, this form is for a non-main thread. Its contents correspond to /proc/<pid>/task/<tid>/:

pid=24831
tid=24832
test -d "/proc/$pid/task/$tid" || {
    printf 'thread %s is no longer present\n' "$tid" >&2
    exit 1
}
readlink "/proc/$pid/task/$tid/cwd"
readlink "/proc/$tid/cwd"
grep -E '^(Name|State|Pid|Tgid):' "/proc/$tid/status"

Both paths describe the same thread while it remains alive. Replace the example numbers with IDs from your own process; never assume that a TID from an old listing can still be reused safely.

A confusing detail is that these /proc/<tid> directories are not returned when a program iterates over /proc. Consequently, ls /proc does not reveal them, even though opening a known numeric path works. Use /proc/<pid>/task when you need an authoritative list of a process's current threads.

4. Use /proc/thread-self for the calling thread

/proc/thread-self/ resolves to the task directory for the thread that is accessing procfs. It is equivalent to /proc/self/task/<tid>/ for that caller, but avoids having to discover and interpolate the caller's TID:

readlink /proc/thread-self

On a system where the command doing the read has PID 24831 and TID 24831, the link target is typically:

24831/task/24831

The exact target varies. The useful check is that the path target identifies the thread doing the read. This is especially helpful for multithreaded programs: /proc/self identifies the process, while /proc/thread-self identifies the current thread.

Common traps and safe recovery

  • PID is not always TID. Use the first and second columns from ps -o pid=,tid= rather than copying one number into every path.
  • Listings become stale. Threads are short-lived. Repeat the lookup and handle a disappearing directory instead of retrying a path indefinitely.
  • Shared does not mean every file is identical. Check the specific entry's semantics. The task view can contain thread-specific attributes that are absent from the parent process directory.
  • A missing /proc/<tid> entry from ls /proc is expected. Enumerate /proc/<pid>/task instead.
  • The main thread can terminate first. In a multithreaded process, the man page documents that /proc/<pid>/task may become unavailable after the main thread exits, even if other threads remain. Capture diagnostics before that point when possible.

These examples change no system state, so there is no undo operation. If a command fails with a permission error, use a process you own or ask the system administrator for an approved diagnostic route. Do not make procfs more permissive just to complete an inspection.

Done means

  • You can list a process's current TIDs under /proc/<pid>/task/.
  • You can distinguish a process PID from an individual thread TID.
  • You can compare /proc/<pid>/task/<tid>/ with a known /proc/<tid>/ path.
  • You can use /proc/thread-self/ when code needs the accessing thread's procfs view.