Read Thread Info with /proc/task and /proc/thread-self

A diagnostic helper that reports on its own thread, or on a worker thread by TID, has to use /proc/PID/task and /proc/thread-self correctly. This walks through listing every thread in a process, reading a thread-specific status file, and using /proc/thread-self without confusing it with /proc/self.

These are read-only checks and normally need no elevated privileges. Allow about ten minutes, including time to adapt the commands to a process you are investigating.

1. Check the local documentation and kernel

The installed reference is Linux man-pages 6.7, package version 6.7-2, and the local entry is named proc_tid(5) even though its canonical page is proc_pid_task(5). The behaviour described here concerns the procfs interfaces documented by that installed page. The directory layout depends on the running kernel and the permissions applied to procfs.

$ man proc_tid
PROC_PID_TASK(5)          Linux Programmer's Manual          PROC_PID_TASK(5)
$ dpkg-query -W -f='${Package} ${Version}\n' manpages
manpages 6.7-2

Checkpoint: if man proc_tid is unavailable, read man proc_pid_task instead. Do not infer a path from a similarly named file on another host.

2. List every thread in a process

/proc/PID/task/ contains one numeric directory per thread in the process. The directory names are thread IDs, or TIDs. A single-threaded process normally has one entry, while a multithreaded process has one entry per live thread.

Start with the shell process running the check. The shell expands $$ to its own process ID, then lists that process's task directory:

$ pid=$$
$ printf 'process ID: %s\n' "$pid"
process ID: 450664
$ ls -1 "/proc/$pid/task"
450664

The number in the example is deliberately variable. On a multithreaded target, expect several numbers. Save the target PID before running a longer investigation, because short-lived processes can exit between commands.

Checkpoint: confirm the directory exists and its entries are numeric. A missing directory usually means the process has exited, the PID was mistyped, or procfs is not mounted at /proc.

3. Read shared and per-thread information

Each /proc/PID/task/TID/ directory exposes files with the same names as the process directory. Some attributes are shared by all threads, others differ per thread. status is a useful first stop because it includes the name, state and the thread's ID fields:

$ pid=REPLACE_WITH_PID
$ tid=REPLACE_WITH_TID
$ sed -n '1,18p' "/proc/$pid/task/$tid/status"
Name:   worker
Umask:  0022
State:  S (sleeping)
Tgid:   12345
Ngid:   0
Pid:    12347
PPid:   12001
TracerPid:      0
Uid:    1000    1000    1000    1000
Gid:    1000    1000    1000    1000
FDSize: 64
Groups: 1000
NStgid: 12345
NSpid:  12345
NSpgid: 12345
NSsid:  12001
Kthread:        0
VmPeak:  123456 kB

The exact fields and values vary with the kernel, namespaces and process state. In particular, Tgid identifies the thread group, while Pid identifies the thread in the relevant PID namespace. Treat the output as a snapshot: a thread can terminate immediately after the directory is found, producing a "No such file or directory" error on the next read.

Security boundary: use an unprivileged account first. If a target's procfs files are protected, do not treat sudo as a harmless fix: elevated access can expose sensitive command, memory and identity information. Get the required authorisation and use the smallest read-only command that answers the question.

4. Understand the direct /proc/TID path

For a running thread that is not the thread-group leader, Linux also accepts /proc/TID/. Its contents correspond to /proc/PID/task/TID/. This path is convenient when the TID is already known, but has a surprising discovery rule: these directories are not returned when a program iterates through /proc, so ls /proc does not show them.

$ tid=REPLACE_WITH_NON_LEADER_TID
$ test -d "/proc/$tid" && echo "direct TID path exists"
direct TID path exists
$ ls -ld "/proc/$tid" "/proc/REPLACE_WITH_PID/task/$tid"
dr-xr-xr-x ... /proc/12347
dr-xr-xr-x ... /proc/12345/task/12347

The permissions, ownership and spacing in ls output are host-specific. What matters is that a live non-leader TID can be used as a pathname. The process ID itself is also a TID, but the special direct directory described by the manual is for threads whose TID differs from the process ID.

Do not use a failed ls /proc/TID lookup to conclude the thread never existed. Recheck /proc/PID/task and the target's lifetime first.

5. Use /proc/thread-self for the accessing thread

/proc/thread-self/ refers to the thread that accesses the path. It is equivalent to that thread's /proc/self/task/TID/ directory. This distinction matters in multithreaded programs: /proc/self names the process as a whole, while /proc/thread-self selects the current accessing thread.

$ readlink /proc/thread-self
450666/task/450666
$ sed -n 's/^Name:[[:space:]]*//p' /proc/thread-self/status
sed

The PID and name above belong to the process performing each access, so they can change between commands. A shell, readlink and sed may be separate processes. For a reliable comparison inside one application, open both paths from that application and compare the results there; do not compare a shell's $$ with a helper program's procfs view.

This path is particularly useful in a library or diagnostic helper that does not know its own numeric TID, and it is safer in examples that may run inside a different PID namespace, because the kernel resolves the special path for the accessing thread.

6. Handle races and the main-thread caveat

Procfs is live state, not a transaction. A thread can exit after you list it, and a PID can later be reused. For diagnostics, capture the process identity and the fields you need in one short operation where possible, then report when the target disappeared.

$ pid=REPLACE_WITH_PID
$ for tid_path in "/proc/$pid/task"/*; do
>     [ -d "$tid_path" ] || continue
>     tid=${tid_path##*/}
>     printf 'TID %s: ' "$tid"
>     sed -n 's/^Name:[[:space:]]*//p' "$tid_path/status" || printf 'thread exited\n'
> done
TID 12345: main
TID 12347: worker

Do not build a long-lived access-control decision from one procfs read. Revalidate the process and thread identity before acting, especially when a numeric PID came from an untrusted source.

There is an additional documented edge case: in a multithreaded process, /proc/PID/task may no longer be available after the main thread has terminated, commonly after it calls pthread_exit(3). If that happens, use the process's own lifecycle and application diagnostics to determine what remains, rather than assuming all worker threads have stopped.

Done means