PTHREADS - Linux手册页

Linux程序员手册 第7部分
更新日期: 2020-08-13

出版信息

这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/

说明

POSIX.1为线程编程(通常称为POSIX线程或Pthread)指定一组接口(函数,头文件)。一个进程可以包含多个线程,所有这些线程都在执行同一程序。这些线程共享相同的全局内存(数据段和堆段),但是每个线程都有自己的堆栈(自动变量)。

POSIX.1还要求线程共享一系列其他属性(即,这些属性是进程范围的,而不是每个线程的):

-
进程ID
-
父进程ID
-
进程组标识和会话标识
-
控制终端
-
用户和组ID
-
打开文件描述符
-
记录锁(请参阅fcntl(2))
-
信号处置
-
文件模式创建掩码(umask(2))
-
当前目录(chdir(2))和根目录(chroot(2))
-
间隔计时器(setitimer(2))和POSIX计时器(timer_create(2))
-
不错的值(setpriority(2))
-
资源限制(setrlimit(2))
-
CPU时间(times(2))和资源(getrusage(2))消耗的度量

与堆栈一样,POSIX.1指定每个线程的各种其他属性是不同的,包括:

-
线程ID(pthread_t数据类型)
-
信号掩码(pthread_sigmask(3))
-
errno变量
-
备用信号栈(sigaltstack(2))
-
实时调度策略和优先级(sched(7))

以下Linux特有功能也是每个线程:

-
功能(请参阅功能(7))
-
CPU关联性(sched_setaffinity(2))

Pthreads function return values

大多数并行线程函数成功返回0,在失败的错误号码。请注意,pthreads函数不会设置errno。对于每个可能返回错误的pthreads函数,POSIX.1-2001指定该函数永远不会因错误EINTR而失败。

Thread IDs

进程中的每个线程都有一个唯一的线程标识符(存储在pthread_t类型中)。该标识符返回给pthread_create(3)的调用者,线程可以使用pthread_self(3)获得其自己的线程标识符。

线程ID仅在一个进程内保证是唯一的。 (在所有接受线程ID作为参数的pthreads函数中,该ID从定义上是指与调用程序处于同一进程中的线程。)

在已终止的线程已加入或已分离的线程已终止之后,系统可以重用线程ID。 POSIX说:"如果应用程序尝试使用其生存期已结束的线程ID,则行为未定义。"

Thread-safe functions

线程安全函数是可以同时从多个线程中安全调用的函数(即,无论是否调用它都将提供相同的结果)。

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指定某些功能必须和某些其它功能可以是取消点。如果线程是可取消的,则其取消类型将被推迟,并且对该线程的取消请求正在处理中,然后当该线程调用作为取消点的函数时,该线程将被取消。

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()
psoirnfo() [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()

一个实现也可以将标准中未指定的其他功能标记为取消点。特别地,一种实现方式可能会将可能阻塞的任何非标准功能标记为取消点。 (这包括可以触摸文件的大多数功能。)

应该注意的是,即使应用程序没有使用异步取消,从异步信号处理程序中调用上述列表中的函数也可能导致异步取消。底层用户代码可能不会期望异步取消,并且用户数据的状态可能会变得不一致。因此,进入延迟取消区域时应谨慎使用信号。

Compiling on Linux

在Linux上,应使用cc -pthread编译使用Pthreads API的程序。

Linux implementations of POSIX threads

随着时间的流逝,Linux上的GNU C库提供了两种线程实现:

LinuxThreads
这是原始的Pthreads实现。从glibc 2.4开始,不再支持此实现。
NPTL(Native POSIX Threads Library)
这是现代的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)创建新的子进程时,任何线程都应该能够在该子进程上等待(2)。但是,该实现只允许创建子项的线程在其上等待(2)。
-
当线程调用execve(2)时,将终止所有其他线程(如POSIX.1所要求)。但是,生成的进程具有与调用execve(2)的线程相同的PID:它应该与主线程具有相同的PID。
-
线程不共享用户和组ID。如果应用程序使用seteuid(2)或类似方法更改其凭据,则这可能会导致设置用户ID程序复杂化,并可能导致Pthreads函数失败。
-
线程不共享公共会话ID和进程组ID。
-
线程不共享使用fcntl(2)创建的记录锁。
-
times(2)和getrusage(2)返回的信息是每个线程而不是整个进程。
-
线程不共享信号撤消值(请参阅semop(2))。
-
线程不共享间隔计时器。
-
线程没有共享一个通用的nice值。
-
POSIX.1区分了指向整个过程的信号和指向各个线程的信号的概念。根据POSIX.1,进程控制信号(例如,使用kill(2)发送)应由进程内的一个任意选择的线程处理。 LinuxThreads不支持过程控制信号的概念:信号只能发送到特定线程。
-
线程具有不同的备用信号堆栈设置。但是,新线程的备用信号堆栈设置是从创建它的线程中复制的,因此这些线程最初共享一个备用信号堆栈。 (新线程应在开始时未定义备用信号堆栈。如果两个线程同时处理其共享备用信号堆栈上的信号,则很可能发生不可预测的程序故障。)

NPTL

使用NPTL,进程中的所有线程都放置在同一线程组中。线程组的所有成员共享相同的PID。 NPTL不使用管理器线程。

NPTL内部使用前两个实时信号。这些信号不能在应用中使用。有关更多详细信息,请参见nptl(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的说明)设置为非无限值,则此值定义新线程的默认堆栈大小。为了有效,必须在执行程序之前设置此限制,可能使用ulimit -s shell内置命令(在C shell中限制stacksize)。

Determining the threading implementation

从glibc 2.3.2开始,可以使用getconf(1)命令确定系统的线程实现,例如:

bash$ getconf GNU_LIBPTHREAD_VERSION
NPTL 2.3.4

对于较早的glibc版本,以下命令应足以确定默认的线程实现:

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

Selecting the threading implementation: 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 aq{print }aq ) | egrep -i aqthreads|nptlaq
        linuxthreads-0.10 by Xavier Leroy

名称

pthreads-POSIX线程

日期:2019-08-20 18:02:01 来源:oir作者:oir