pthreads(7)

๐ŸŒˆ pthreads(7) โ€” POSIX threads ๐ŸŒˆ

okay so bestie, welcome to the Miscellaneous Information Manual, section 7 โ€” yes, that same manual name is right there in the header of the original and we are not skipping it, it's part of the lore ๐Ÿซถ. This page is the whole vibe of threading on Unix and it's long, so we're doing it in three parts and you're gonna know it by heart. Let's go ๐Ÿ’…


๐Ÿ’– NAME ๐Ÿ’–

pthreads โ€” POSIX threads


๐Ÿฆ‹ DESCRIPTION ๐Ÿฆ‹

okay so. POSIX.1 literally specifies a whole bundle of interfaces โ€” functions and header files โ€” for doing threaded programming, and the entire world calls this POSIX threads, or Pthreads for short. One single process can hold multiple threads, and every one of those threads is running the exact same program. Here's the tea: these threads share the same global memory (that's the data and heap segments), but each thread gets its own stack (which is where your automatic variables live). no cap.

POSIX.1 also says threads have to share a whole range of other attributes โ€” meaning these are process-wide, not per-thread:

and then, on top of the stack, POSIX.1 says these other attributes are distinct for each thread:

and because Linux is extra, these are per-thread too:


๐Ÿ’… Pthreads function return values ๐Ÿ’…

Most pthreads functions return 0 when they slay, and an error number when they don't. Those error numbers mean exactly the same thing as the error numbers your regular system calls and C library functions drop into errno. BIG note though: the pthreads functions do not set errno ๐Ÿ™ˆ. And for every pthreads function that can return an error, POSIX.1-2001 is very clear: the function can never fail with the error EINTR. periodt.


๐ŸŽ€ Thread IDs ๐ŸŽ€

Every single thread in a process has its own unique thread identifier, and it's stored in the type pthread_t. That identifier gets handed back to whoever called pthread_create(3), and a thread can grab its own identifier with pthread_self(3) ๐ŸŒธ.

Thread IDs are only guaranteed to be unique within a process. (And in every pthreads function that takes a thread ID as an argument, that ID by definition refers to a thread in the same process as the caller โ€” so yes, it's always your own house.)

The system may reuse a thread ID once a terminated thread has been joined, or once a detached thread has terminated. POSIX says, and I quote: "If an application attempts to use a thread ID whose lifetime has ended, the behavior is undefined." ๐Ÿฉธ so like, do not do that. undefined behavior is not the vibe.


๐Ÿงš Thread-safe functions ๐Ÿงš

A thread-safe function is one you can safely call from multiple threads at the same time โ€” and by "safely" we mean it will deliver the same results regardless of whether it's being called concurrently or not. iconic behavior.

POSIX.1-2001 and POSIX.1-2008 require that all functions specified in the standard shall be thread-safe, with these exceptions right here (yes, the whole list, we are not skipping a single one ๐Ÿซถ):

asctime()
basename()
catgets()
crypt()
ctermid() if passed a non-NULL argument
ctime()
dbm_clearerr()
dbm_close()
dbm_delete()
dbm_error()
dbm_fetch()
dbm_firstkey()
dbm_nextkey()
dbm_open()
dbm_store()
dirname()
dlerror()
drand48()
ecvt() [POSIX.1-2001 only (function removed in POSIX.1-2008)]
encrypt()
endgrent()
endpwent()
endutxent()
fcvt() [POSIX.1-2001 only (function removed in POSIX.1-2008)]
ftw()
gcvt() [POSIX.1-2001 only (function removed in POSIX.1-2008)]
getc_unlocked()
getchar_unlocked()
getdate()
getenv()
getgrent()
getgrgid()
getgrnam()
gethostbyaddr() [POSIX.1-2001 only (function removed in POSIX.1-2008)]
gethostbyname() [POSIX.1-2001 only (function removed in POSIX.1-2008)]
gethostent()
getlogin()
getnetbyaddr()
getnetbyname()
getnetent()
getopt()
getprotobyname()
getprotobynumber()
getprotoent()
getpwent()
getpwnam()
getpwuid()
getservbyname()
getservbyport()
getservent()
getutxent()
getutxid()
getutxline()
gmtime()
hcreate()
hdestroy()
hsearch()
inet_ntoa()
l64a()
lgamma()
lgammaf()
lgammal()
localeconv()
localtime()
lrand48()
mrand48()
nftw()
nl_langinfo()
ptsname()
putc_unlocked()
putchar_unlocked()
putenv()
pututxline()
rand()
readdir()
setenv()
setgrent()
setkey()
setpwent()
setutxent()
strerror()
strsignal() [Added in POSIX.1-2008]
strtok()
system() [Added in POSIX.1-2008]
tmpnam() if passed a non-NULL argument
ttyname()
unsetenv()
wcrtomb() if its final argument is NULL
wcsrtombs() if its final argument is NULL
wcstombs()
wctomb()

๐Ÿฆ‹ Async-cancel-safe functions ๐Ÿฆ‹

An async-cancel-safe function is one you can safely call in an application where asynchronous cancelability is turned on (see pthread_setcancelstate(3)).

POSIX.1-2001 and POSIX.1-2008 require only these three to be async-cancel-safe:

pthread_cancel()
pthread_setcancelstate()
pthread_setcanceltype()

๐Ÿ”ฅ Cancelation points ๐Ÿ”ฅ

POSIX.1 says some functions must be cancelation points, and some other functions may be cancelation points. Here's the mechanics, bestie: if a thread is cancelable, its cancelability type is deferred, and there's a cancelation request pending for that thread โ€” then the thread gets canceled the moment it calls a function that's a cancelation point. ๐Ÿ’€

These functions are required to be cancelation points by POSIX.1-2001 and/or POSIX.1-2008:

accept()
aio_suspend()
clock_nanosleep()
close()
connect()
creat()
fcntl() F_SETLKW
fdatasync()
fsync()
getmsg()
getpmsg()
lockf() F_LOCK
mq_receive()
mq_send()
mq_timedreceive()
mq_timedsend()
msgrcv()
msgsnd()
msync()
nanosleep()
open()
openat() [Added in POSIX.1-2008]
pause()
poll()
pread()
pselect()
pthread_cond_timedwait()
pthread_cond_wait()
pthread_join()
pthread_testcancel()
putmsg()
putpmsg()
pwrite()
read()
readv()
recv()
recvfrom()
recvmsg()
select()
sem_timedwait()
sem_wait()
send()
sendmsg()
sendto()
sigpause() [POSIX.1-2001 only (moves to "may" list in POSIX.1-2008)]
sigsuspend()
sigtimedwait()
sigwait()
sigwaitinfo()
sleep()
system()
tcdrain()
usleep() [POSIX.1-2001 only (function removed in POSIX.1-2008)]
wait()
waitid()
waitpid()
write()
writev()

and these functions may be cancelation points according to POSIX.1-2001 and/or POSIX.1-2008:

okay so here's the whole list of cancelation points, and bestie, it's long. but we're gonna get through it together, no cap. ๐Ÿ’…

  • access()
  • asctime()
  • asctime_r()
  • catclose()
  • catgets()
  • catopen()
  • chmod() [Added in POSIX.1-2008]
  • chown() [Added in POSIX.1-2008]
  • closedir()
  • closelog()
  • ctermid()
  • ctime()
  • ctime_r()
  • dbm_close()
  • dbm_delete()
  • dbm_fetch()
  • dbm_nextkey()
  • dbm_open()
  • dbm_store()
  • dlclose()
  • dlopen()
  • dprintf() [Added in POSIX.1-2008]
  • endgrent()
  • endhostent()
  • endnetent()
  • endprotoent()
  • endpwent()
  • endservent()
  • endutxent()
  • faccessat() [Added in POSIX.1-2008]
  • fchmod() [Added in POSIX.1-2008]
  • fchmodat() [Added in POSIX.1-2008]
  • fchown() [Added in POSIX.1-2008]
  • fchownat() [Added in POSIX.1-2008]
  • fclose()
  • fcntl() (for any value of cmd argument)
  • fflush()
  • fgetc()
  • fgetpos()
  • fgets()
  • fgetwc()
  • fgetws()
  • fmtmsg()
  • fopen()
  • fpathconf()
  • fprintf()
  • fputc()
  • fputs()
  • fputwc()
  • fputws()
  • fread()
  • freopen()
  • fscanf()
  • fseek()
  • fseeko()
  • fsetpos()
  • fstat()
  • fstatat() [Added in POSIX.1-2008]
  • ftell()
  • ftello()
  • ftw()
  • futimens() [Added in POSIX.1-2008]
  • fwprintf()
  • fwrite()
  • fwscanf()
  • getaddrinfo()
  • getc()
  • getc_unlocked()
  • getchar()
  • getchar_unlocked()
  • getcwd()
  • getdate()
  • getdelim() [Added in POSIX.1-2008]
  • getgrent()
  • getgrgid()
  • getgrgid_r()
  • getgrnam()
  • getgrnam_r()
  • gethostbyaddr() [POSIX.1-2001 only (function removed in POSIX.1-2008)]
  • gethostbyname() [POSIX.1-2001 only (function removed in POSIX.1-2008)]
  • gethostent()
  • gethostid()
  • gethostname()
  • getline() [Added in POSIX.1-2008]
  • getlogin()
  • getlogin_r()
  • getnameinfo()
  • getnetbyaddr()
  • getnetbyname()
  • getnetent()
  • getopt() (if opterr is nonzero)
  • getprotobyname()
  • getprotobynumber()
  • getprotoent()
  • getpwent()
  • getpwnam()
  • getpwnam_r()
  • getpwuid()
  • getpwuid_r()
  • gets()
  • getservbyname()
  • getservbyport()
  • getservent()
  • getutxent()
  • getutxid()
  • getutxline()
  • getwc()
  • getwchar()
  • getwd() [POSIX.1-2001 only (function removed in POSIX.1-2008)]
  • glob()
  • iconv_close()
  • iconv_open()
  • ioctl()
  • link()
  • linkat() [Added in POSIX.1-2008]
  • lio_listio() [Added in POSIX.1-2008]
  • localtime()
  • localtime_r()
  • lockf() [Added in POSIX.1-2008]
  • lseek()
  • lstat()
  • mkdir() [Added in POSIX.1-2008]
  • mkdirat() [Added in POSIX.1-2008]
  • mkdtemp() [Added in POSIX.1-2008]
  • mkfifo() [Added in POSIX.1-2008]
  • mkfifoat() [Added in POSIX.1-2008]
  • mknod() [Added in POSIX.1-2008]
  • mknodat() [Added in POSIX.1-2008]
  • mkstemp()
  • mktime()
  • nftw()
  • opendir()
  • openlog()
  • pathconf()
  • pclose()
  • perror()
  • popen()
  • posix_fadvise()
  • posix_fallocate()
  • posix_madvise()
  • posix_openpt()
  • posix_spawn()
  • posix_spawnp()
  • posix_trace_clear()
  • posix_trace_close()
  • posix_trace_create()
  • posix_trace_create_withlog()
  • posix_trace_eventtypelist_getnext_id()
  • posix_trace_eventtypelist_rewind()
  • posix_trace_flush()
  • posix_trace_get_attr()
  • posix_trace_get_filter()
  • posix_trace_get_status()
  • posix_trace_getnext_event()
  • posix_trace_open()
  • posix_trace_rewind()
  • posix_trace_set_filter()
  • posix_trace_shutdown()
  • posix_trace_timedgetnext_event()
  • posix_typed_mem_open()
  • printf()
  • psiginfo() [Added in POSIX.1-2008]
  • psignal() [Added in POSIX.1-2008]
  • pthread_rwlock_rdlock()
  • pthread_rwlock_timedrdlock()
  • pthread_rwlock_timedwrlock()
  • pthread_rwlock_wrlock()
  • putc()
  • putc_unlocked()
  • putchar()
  • putchar_unlocked()
  • puts()
  • pututxline()
  • putwc()
  • putwchar()
  • readdir()
  • readdir_r()
  • readlink() [Added in POSIX.1-2008]
  • readlinkat() [Added in POSIX.1-2008]
  • remove()
  • rename()
  • renameat() [Added in POSIX.1-2008]
  • rewind()
  • rewinddir()
  • scandir() [Added in POSIX.1-2008]
  • scanf()
  • seekdir()
  • semop()
  • setgrent()
  • sethostent()
  • setnetent()
  • setprotoent()
  • setpwent()
  • setservent()
  • setutxent()
  • sigpause() [Added in POSIX.1-2008]
  • stat()
  • strerror()
  • strerror_r()
  • strftime()
  • symlink()
  • symlinkat() [Added in POSIX.1-2008]
  • sync()
  • syslog()
  • tmpfile()
  • tmpnam()
  • ttyname()
  • ttyname_r()
  • tzset()
  • ungetc()
  • ungetwc()
  • unlink()
  • unlinkat() [Added in POSIX.1-2008]
  • utime() [Added in POSIX.1-2008]
  • utimensat() [Added in POSIX.1-2008]
  • utimes() [Added in POSIX.1-2008]
  • vdprintf() [Added in POSIX.1-2008]
  • vfprintf()
  • vfwprintf()
  • vprintf()
  • vwprintf()
  • wcsftime()
  • wordexp()
  • wprintf()
  • wscanf()

An implementation may also mark other functions that aren't in the standard as cancelation points. like, any nonstandard function that might block is probably gonna be a cancelation point (and that includes most functions that can touch files, fr fr). ๐Ÿ‘€

And heads up: even if your app isn't using asynchronous cancelation, calling any of these functions from an asynchronous signal handler can basically cause the same thing as asynchronous cancelation. and your user code might not be expecting that, so the user data could get all inconsistent. so yeah, use signals with caution when you're in a region of deferred cancelation. ๐Ÿšจ


โœจ Compiling on Linux โœจ

On Linux, if your program uses the Pthreads API, you should compile it with cc -pthread. that's it. simple. ๐ŸŽ€

โœจ Linux implementations of POSIX threads โœจ

So over time, the GNU C library on Linux has given us two threading implementations:

  • LinuxThreads โ€” this is the original Pthreads implementation. but since glibc 2.4, it's no longer supported. rip. ๐Ÿ’€
  • NPTL (Native POSIX Threads Library) โ€” this is the modern one. compared to LinuxThreads, NPTL is way more compliant with POSIX.1 and performs better when you're creating a ton of threads. NPTL has been available since glibc 2.3.2, and it needs features that are in the Linux 2.6 kernel. slay. โœจ

Both of these are so-called 1:1 implementations, which means each thread maps to a kernel scheduling entity. and both use the Linux clone(2) system call. in NPTL, thread synchronization primitives (mutexes, thread joining, and so on) are implemented using the Linux futex(2) system call. ๐Ÿงต

โœจ LinuxThreads โœจ

The notable features of this implementation are:

  • In addition to the main (initial) thread and the threads your program creates with pthread_create(3), it creates a "manager" thread. this thread handles thread creation and termination. (if this thread gets killed by accident, problems can happen. ๐Ÿ™ˆ)
  • Signals are used internally by the implementation. on Linux 2.2 and later, the first three real-time signals are used (see also signal(7)). on older Linux kernels, SIGUSR1 and SIGUSR2 are used. applications must avoid using whichever set of signals the implementation is using. โš ๏ธ
  • Threads do not share process IDs. (basically, LinuxThreads threads are implemented as processes that share more info than usual, but they don't share a common process ID.) LinuxThreads threads (including the manager thread) show up as separate processes when you use ps(1). ๐Ÿ‘€

The LinuxThreads implementation deviates from the POSIX.1 spec in a bunch of ways, including:

  • Calls to getpid(2) return a different value in each thread.
  • Calls to getppid(2) in threads other than the main thread return the process ID of the manager thread; instead, getppid(2) in these threads should return the same value as getppid(2) in the main thread.
  • When one thread creates a new child process using fork(2), any thread should be able to wait(2) on the child. However, this implementation only lets the thread that created the child wait(2) on it.
  • When a thread calls execve(2), all other threads are terminated (as POSIX.1 requires). However, the resulting process has the same PID as the thread that called execve(2): it should have the same PID as the main thread.
  • Threads do not share user and group IDs. this can cause complications with set-user-ID programs and can cause failures in Pthreads functions if an application changes its credentials using seteuid(2) or similar.
  • Threads do not share a common session ID and process group ID.
  • Threads do not share record locks created using fcntl(2).
  • The information returned by times(2) and getrusage(2) is per-thread rather than process-wide.
  • Threads do not share semaphore undo values (see semop(2)).
  • Threads do not share interval timers.
  • Threads do not share a common nice value.
  • POSIX.1 distinguishes between signals directed to the process as a whole and signals directed to individual threads. According to POSIX.1, a process-directed signal (sent using kill(2), for example) should be handled by a single, arbitrarily selected thread within the process. LinuxThreads does not support the notion of process-directed signals: signals may only be sent to specific threads.

  • alt signal stacks are per-thread, but the copy is real โ€” threads have distinct alternate signal stack settings. But a new thread's alternate signal stack settings get copied from the thread that created it, so the threads initially share an alternate signal stack. (A new thread should start with no alternate signal stack defined. If two threads handle signals on their shared alternate signal stack at the same time, unpredictable program failures are likely to occur.) so like, technically separate, functionally a shared swimming pool, be careful bestie ๐Ÿ’€

๐Ÿ’– NPTL ๐Ÿ’–

With NPTL, all of the threads in a process are placed in the same thread group; all members of a thread group share the same PID. one friend group, one ID, no solo acts. NPTL does not employ a manager thread.

NPTL makes internal use of the first two real-time signals; these signals cannot be used in applications. See nptl(7) for further details. not yours. they're taken. โœ‹

NPTL still has at least one nonconformance with POSIX.1:

  • Threads do not share a common nice value. everyone's got their own priority vibe, even in the same process ๐Ÿ˜ญ

Some NPTL nonconformances occur only with older kernels:

  • The information returned by times(2) and getrusage(2) is per-thread rather than process-wide (fixed in Linux 2.6.9).
  • Threads do not share resource limits (fixed in Linux 2.6.10).
  • Threads do not share interval timers (fixed in Linux 2.6.12).
  • Only the main thread is permitted to start a new session using setsid(2) (fixed in Linux 2.6.16).
  • Only the main thread is permitted to make the process into a process group leader using setpgid(2) (fixed in Linux 2.6.16).
  • Threads have distinct alternate signal stack settings. However, a new thread's alternate signal stack settings are copied from the thread that created it, so that the threads initially share an alternate signal stack (fixed in Linux 2.6.16).

Note the following further points about the NPTL implementation:

  • If the stack size soft resource limit (see the description of RLIMIT_STACK in setrlimit(2)) is set to a value other than unlimited, then this value defines the default stack size for new threads. so it's not a random number, it's your rlimit stepping in ๐Ÿซถ To be effective, this limit must be set before the program is executed, perhaps using the ulimit -s shell built-in command (limit stacksize in the C shell). set it late and it does nothing. timing is everything, periodt.

๐Ÿ”Ž Determining the threading implementation ๐Ÿ”Ž

Since glibc 2.3.2, the getconf(1) command can be used to determine the system's threading implementation, for example:

bash$ getconf GNU_LIBPTHREAD_VERSION
NPTL 2.3.4

With older glibc versions, a command such as the following should be sufficient to determine the default threading implementation:

bash$ $( ldd /bin/ls | grep libc.so | awk '{print $3}' ) | \
                egrep -i 'threads|nptl'
        Native POSIX Threads Library by Ulrich Drepper et al

๐Ÿงš Selecting the threading implementation: LD_ASSUME_KERNEL ๐Ÿงš

On systems with a glibc that supports both LinuxThreads and NPTL (i.e., glibc 2.3.*), the LD_ASSUME_KERNEL environment variable can be used to override the dynamic linker's default choice of threading implementation. the override button. use responsibly ๐Ÿ’… This variable tells the dynamic linker to assume that it is running on top of a particular kernel version. By specifying a kernel version that does not provide the support required by NPTL, we can force the use of LinuxThreads. (The most likely reason for doing this is to run a (broken) application that depends on some nonconformant behavior in LinuxThreads.) yes bestie, the "broken" is in the source and it's staying there For example:

bash$ $( LD_ASSUME_KERNEL=2.2.5 ldd /bin/ls | grep libc.so | \
                awk '{print $3}' ) | egrep -i 'threads|nptl'
        linuxthreads-0.10 by Xavier Leroy

๐Ÿ‘€ SEE ALSO ๐Ÿ‘€

clone(2), fork(2), futex(2), gettid(2), proc(5), attributes(7), futex(7), nptl(7), sigevent(3type), signal(7)

Various Pthreads manual pages, for example: pthread_atfork(3), pthread_attr_init(3), pthread_cancel(3), pthread_cleanup_push(3), pthread_cond_signal(3), pthread_cond_wait(3), pthread_create(3), pthread_detach(3), pthread_equal(3), pthread_exit(3), pthread_key_create(3), pthread_kill(3), pthread_mutex_lock(3), pthread_mutex_unlock(3), pthread_mutexattr_destroy(3), pthread_mutexattr_init(3), pthread_once(3), pthread_spin_init(3), pthread_spin_lock(3), pthread_rwlockattr_setkind_np(3), pthread_setcancelstate(3), pthread_setcanceltype(3), pthread_setspecific(3), pthread_sigmask(3), pthread_sigqueue(3), and pthread_testcancel(3)`


and that's a wrap on pthreads(7) ๐ŸŽ€ this lil masterpiece ships with Linux man-pages 6.20, dated 2026-02-08 โ€” go read it, love her, respect her, and remember: the vibe changes but the facts never do ๐Ÿ’–โœจ

๐Ÿ“ฆ where this page comes from
  • arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
  • nixpkgs unstable: man-pages 6.20
  • ๐Ÿ“œ the page's license: Linux-man-pages-copyleft
  • ๐Ÿ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms