pthreads(7) - Linux 手册页
名称
pthreads - POSIX 线程
描述
POSIX.1 指定了一套用于线程编程的接口(函数、头文件),通常称为 POSIX 线程或 Pthreads。单个进程可以包含多个线程,所有线程执行相同的程序。这些线程共享相同的全局内存(数据段和堆段),但每个线程拥有自己的栈(自动变量)。
POSIX.1 还要求线程共享一系列其他属性(即,这些属性是进程级的,而不是线程级的)
- -
进程 ID
-
父进程 ID
-
进程组 ID 和会话 ID
-
控制终端
-
用户 ID 和组 ID
-
打开的文件描述符
-
记录锁(参见 fcntl(2))
-
信号处置
-
文件模式创建掩码(umask(2))
-
-
间隔计时器(setitimer(2))和 POSIX 计时器
- (timer_create(2))
- -
nice 值(setpriority(2))
-
资源限制(setrlimit(2))
-
- 除了栈之外,POSIX.1 指定了其他各种属性对于每个线程是独立的,包括
- -
线程 ID(pthread_t 数据类型)
-
信号掩码(pthread_sigmask(3))
-
errno 变量
-
备用信号栈(sigaltstack(2))
-
实时调度策略和优先级(sched_setscheduler(2) 和 sched_setparam(2))
- 以下 Linux 特有功能也是线程级的
- -
能力 (capabilities)(参见 capabilities(7))
-
CPU 亲和性(sched_setaffinity(2))
Pthreads 函数返回值
- 大多数 pthreads 函数在成功时返回 0,失败时返回错误编号。请注意,pthreads 函数不会设置 errno。对于每个可能返回错误的 pthreads 函数,POSIX.1-2001 指定该函数绝不会因错误 EINTR 而失败。
线程 ID
- 进程中的每个线程都有一个唯一的线程标识符(存储在 pthread_t 类型中)。此标识符会返回给 pthread_create(3) 的调用者,线程可以使用 pthread_self(3) 获取自己的线程标识符。线程 ID 仅保证在进程内唯一。线程 ID 在已终止的线程被连接(joined)或分离(detached)线程终止后可能会被重用。在所有接受线程 ID 作为参数的 pthreads 函数中,该 ID 按定义指向同一进程中的线程。
线程安全函数
- 线程安全函数是指可以安全地(即无论是否同时从多个线程调用,都能产生相同的结果)被多个线程调用的函数。
POSIX.1-2001 和 POSIX.1-2008 要求标准中指定的所有函数都必须是线程安全的,以下函数除外
-
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)
- 异步取消安全函数是指可以在启用了异步可取消性的应用程序中安全调用的函数(参见 pthread_setcancelstate(3))。
POSIX.1-2001 和 POSIX.1-2008 仅要求以下函数必须是异步取消安全的
-
pthread_cancel() pthread_setcancelstate() pthread_setcanceltype()
取消点 (Cancellation points)
- POSIX.1 指定某些函数必须是,某些其他函数可以是取消点。如果线程是可取消的,其取消类型为延迟(deferred),且该线程有待处理的取消请求,则当线程调用属于取消点的函数时,线程将被取消。
POSIX.1-2001 和/或 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()
- 根据 POSIX.1-2001 和/或 POSIX.1-2008,以下函数可以是取消点
-
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() [SUSv3 only (function removed in POSIX.1-2008)] gethostbyname() [SUSv3 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() [SUSv3 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()
- 实现也可以将标准中未指定的其他函数标记为取消点。特别是,实现很可能会将任何可能阻塞的非标准函数标记为取消点。(这包括大多数可以触及文件的函数。)
在 Linux 上编译
- 在 Linux 上,使用 Pthreads API 的程序应使用 cc -pthread 进行编译。
POSIX 线程的 Linux 实现
- 随着时间的推移,Linux 上的 GNU C 库提供了两种线程实现
- LinuxThreads
- 这是最初的 Pthreads 实现。自 glibc 2.4 起,不再支持此实现。
- NPTL (Native POSIX Threads Library,原生 POSIX 线程库)
- 这是现代的 Pthreads 实现。与 LinuxThreads 相比,NPTL 更符合 POSIX.1 规范的要求,并且在创建大量线程时具有更好的性能。NPTL 自 glibc 2.3.2 起可用,并需要 Linux 2.6 内核中存在的功能。
- 这两种实现都是所谓的 1:1 实现,意味着每个线程映射到一个内核调度实体。两种线程实现都使用了 Linux clone(2) 系统调用。在 NPTL 中,线程同步原语(互斥锁、线程连接等)是使用 Linux futex(2) 系统调用实现的。
LinuxThreads
- 该实现的主要特点如下
- -
除了主(初始)线程以及程序使用 pthread_create(3) 创建的线程外,该实现还会创建一个“管理”线程。此线程处理线程的创建和终止。(如果此线程被意外杀死,可能会导致问题。)
-
实现内部使用了信号。在 Linux 2.2 及更高版本上,使用了前三个实时信号(参见 signal(7))。在较旧的 Linux 内核上,使用 SIGUSR1 和 SIGUSR2。应用程序必须避免使用该实现所采用的任何一组信号。
-
线程不共享进程 ID。(实际上,LinuxThreads 线程实现为共享比普通进程更多信息的进程,但不共享公共进程 ID。)使用 ps(1) 可以将 LinuxThreads 线程(包括管理线程)视为独立的进程。
- LinuxThreads 的实现与 POSIX.1 规范在许多方面存在偏差,包括以下内容
- -
对 getpid(2) 的调用在每个线程中返回不同的值。
-
在主线程以外的线程中调用 getppid(2) 会返回管理线程的进程 ID;而这些线程中的 getppid(2) 本应返回与主线程中 getppid(2) 相同的值。
-
当一个线程使用 fork(2) 创建新的子进程时,任何线程都应该能够对子进程执行 wait(2)。然而,该实现只允许创建子进程的线程对它执行 wait(2)。
-
当一个线程调用 execve(2) 时,所有其他线程都会被终止(正如 POSIX.1 所要求的那样)。然而,产生的进程具有与调用 execve(2) 的线程相同的 PID:它本应具有与主线程相同的 PID。
-
线程不共享用户 ID 和组 ID。这会导致设置用户 ID (set-user-ID) 程序的复杂化,并且如果应用程序使用 seteuid(2) 或类似函数更改其凭据,可能会导致 Pthreads 函数失败。
-
线程不共享公共会话 ID 和进程组 ID。
-
线程不共享使用 fcntl(2) 创建的记录锁。
-
times(2) 和 getrusage(2) 返回的信息是线程级的,而不是进程级的。
-
线程不共享信号量撤销值(参见 semop(2))。
-
线程不共享间隔计时器。
-
线程不共享公共的 nice 值。
-
POSIX.1 区分了指向整个进程的信号和指向单个线程的信号的概念。根据 POSIX.1,进程定向信号(例如使用 kill(2) 发送)应由进程中任意选定的单个线程处理。LinuxThreads 不支持进程定向信号的概念:信号只能发送给特定线程。
-
线程具有不同的备用信号栈设置。然而,新线程的备用信号栈设置是从创建它的线程复制而来的,因此线程最初共享一个备用信号栈。(新线程开始时应无定义的备用信号栈。如果两个线程同时在其共享的备用信号栈上处理信号,很可能会发生不可预知的程序故障。)
NPTL
- 使用 NPTL,进程中的所有线程都被置于同一个线程组中;所有线程组成员共享相同的 PID。NPTL 不使用管理线程。NPTL 在内部使用了前两个实时信号(参见 signal(7));这些信号不能在应用程序中使用。
NPTL 仍然至少有一项与 POSIX.1 不一致的地方
- -
线程不共享公共的 nice 值。
- 一些 NPTL 的不一致仅在较旧的内核中出现
- -
times(2) 和 getrusage(2) 返回的信息是线程级的,而不是进程级的(在内核 2.6.9 中修复)。
-
线程不共享资源限制(在内核 2.6.10 中修复)。
-
线程不共享间隔计时器(在内核 2.6.12 中修复)。
-
只有主线程被允许使用 setsid(2) 启动新会话(在内核 2.6.16 中修复)。
-
只有主线程被允许使用 setpgid(2) 将进程变成进程组领导者(在内核 2.6.16 中修复)。
-
线程具有不同的备用信号栈设置。然而,新线程的备用信号栈设置是从创建它的线程复制而来的,因此线程最初共享一个备用信号栈(在内核 2.6.16 中修复)。
- 关于 NPTL 实现,请注意以下进一步几点
- -
如果栈大小软资源限制(参见 setrlimit(2) 中 RLIMIT_STACK 的说明)被设置为非 unlimited 的值,则此值定义了新线程的默认栈大小。要使其生效,必须在程序执行前设置此限制,或许可以使用 shell 内置命令 ulimit -s(C shell 中为 limit stacksize)。
确定线程实现
- 自 glibc 2.3.2 起,可以使用 getconf(1) 命令来确定系统的线程实现,例如
bash$ getconf GNU_LIBPTHREAD_VERSION NPTL 2.3.4
对于较旧的 glibc 版本,类似以下命令应足以确定默认线程实现bash$ $( ldd /bin/ls | grep libc.so | awk '{print $3}' ) | \ egrep -i 'threads|nptl' Native POSIX Threads Library by Ulrich Drepper et al
选择线程实现:LD_ASSUME_KERNEL
- 在支持 LinuxThreads 和 NPTL 的 glibc 系统上(即 glibc 2.3.x),可以使用 LD_ASSUME_KERNEL 环境变量来覆盖动态链接器默认选择的线程实现。该变量告诉动态链接器假设它正在特定内核版本之上运行。通过指定一个不提供 NPTL 所需支持的内核版本,我们可以强制使用 LinuxThreads。(这样做最可能的原因是运行依赖于 LinuxThreads 中某些非一致性行为的(损坏的)应用程序。)例如
bash$ $( LD_ASSUME_KERNEL=2.2.5 ldd /bin/ls | grep libc.so | \ awk '{print $3}' ) | egrep -i 'threads|ntpl' linuxthreads-0.10 by Xavier Leroy
参见
clone(2), futex(2), gettid(2), proc(5), futex(7), sigevent(7), signal(7),
各种 Pthreads 手册页,例如:pthread_attr_init(3), pthread_atfork(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_once(3), pthread_setcancelstate(3), pthread_setcanceltype(3), pthread_setspecific(3), pthread_sigmask(3), pthread_sigqueue(3), 以及 pthread_testcancel(3)