在多线程编程和信号处理过程中,经常会遇到可重入(reentrance)与线程安全(thread-safe)。
很多人纠结于reentrance和thread-safe两个概念理解纠缠不清。我想救我对reentrance和thread-safe的理解作个总结
一、可重入(reentrance)
首先来看下APUE中,列出的可重入函数:
accept |
fchmod |
lseek |
sendto |
stat |
access |
fchown |
lstat |
setgid |
symlink |
aio_error |
fcntl |
mkdir |
setpgid |
sysconf |
aio_return |
fdatasync |
mkfifo |
setsid |
tcdrain |
aio_suspend |
fork |
open |
setsockopt |
tcflow |
alarm |
fpathconf |
pathconf |
setuid |
tcflush |
bind |
fstat |
pause |
shutdown |
tcgetattr |
cfgetispeed |
fsync |
pipe |
sigaction |
tcgetpgrp |
cfgetospeed |
ftruncate |
poll |
sigaddset |
tcsendbreak |
cfsetispeed |
getegid |
posix_trace_event |
sigdelset |
tcsetattr |
cfsetospeed |
geteuid |
pselect |
sigemptyset |
tcsetpgrp |
chdir |
getgid |
raise |
sigfillset |
time |
chmod |
getgroups |
read |
sigismember |
timer_getoverrun |
chown |
getpeername |
readlink |
signal |
timer_gettime |
clock_gettime |
getpgrp |
recv |
sigpause |
timer_settime |
close |
getpid |
recvfrom |
sigpending |
times |
connect |
getppid |
recvmsg |
sigprocmask |
umask |
creat |
getsockname |
rename |
sigqueue |
uname |
dup |
getsockopt |
rmdir |
sigset |
unlink |
dup2 |
getuid |
select |
sigsuspend |
utime |
execle |
kill |
sem_post |
sleep |
wait |
execve |
link |
send |
socket |
waitpid |
_Exit & _exit |
listen |
sendmsg |
socketpair |
write |
以上表中的这些函数,都是可重入的。
那么究竟什么是可重入函数呢?
我的理解:可重入函数,与多线程无关,即可重入概念并不依赖于多线程,可重入的提出时依据单一线程提出来的,当然,多线程可重入是他的扩展。一个函数被同一个线程调用2次以上,得到的结果具有可再现性(多次调用函数,得到的结果是一样的)。那么我们说这个函数是可重入的。
可重入,并不一定要是多线程的。可重入只关注一个结果可再现性。在APUE中,可函数可重入的概念最先是在讲signal的handler的时候提出的。此时进程(线程)正在执行函数fun(),在函数fun()还未执行完的时候,突然进程接收到一个信号sig, 此时,需要暂停执行fun(),要转而执行sig信号的处理函数sig_handler(),那么,如果在sig_handler()中,也恰好调用了函数fun().信号的处理是以软终端的形式进行的,那么,当sig_handler()执行完返回之后,CPU会继续从fun()被打断的地方往下执行。这里讲的比较特殊,最好的情况是,进程中调用了fun(),函数,信号处理函数sig_handle()中也调用了fun()。如果fun()函数是可重入的,那么,多次调用fun()函数就具有可再现性。从而,两次调用fun()的结果是正确的预期结果。非可重入函数,则恰好相反。
简而言之,可重入函数,描述的是函数被多次调用但是结果具有可再现性。
如果fun(),中,使用了static变量、返回全局变量、调用非可重入函数等等,带有全局性的操作,都将会导致2次以上调用fun()的结果的不可再现性(当然,有些时候使用了static、全局变量等等,不一定导致调用结果不可再现性)。只要使调用结果具有可再现性,那么该函数就是可重入的。
为了保证函数是可重入的,需要做到一下几点:
1,不在函数内部使用静态或者全局数据
2,不返回静态或者全局数据,所有的数据都由函数调用者提供
3,使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据
4, 如果必须访问全局数据,使用互斥锁来保护
5,不调用不可重入函数
二,函数线程安全
看看APUE上,描述的非线程安全函数
asctime |
ecvt |
gethostent |
getutxline |
putc_unlocked |
basename |
encrypt |
getlogin |
gmtime |
putchar_unlocked |
catgets |
endgrent |
getnetbyaddr |
hcreate |
putenv |
crypt |
endpwent |
getnetbyname |
hdestroy |
pututxline |
ctime |
endutxent |
getnetent |
hsearch |
rand |
dbm_clearerr |
fcvt |
getopt |
inet_ntoa |
readdir |
dbm_close |
ftw |
getprotobyname |
l64a |
setenv |
dbm_delete |
gcvt |
getprotobynumber |
lgamma |
setgrent |
dbm_error |
getc_unlocked |
getprotoent |
lgammaf |
setkey |
dbm_fetch |
getchar_unlocked |
getpwent |
lgammal |
setpwent |
dbm_firstkey |
getdate |
getpwnam |
localeconv |
setutxent |
dbm_nextkey |
getenv |
getpwuid |
localtime |
strerror |
dbm_open |
getgrent |
getservbyname |
lrand48 |
strtok |
dbm_store |
getgrgid |
getservbyport |
mrand48 |
ttyname |
dirname |
getgrnam |
getservent |
nftw |
unsetenv |
dlerror |
gethostbyaddr |
getutxent |
nl_langinfo |
wcstombs |
drand48 |
gethostbyname |
getutxid |
ptsname |
wctomb |
If a function can be safely called by multiple threads at the same time, we say that the function is thread-safe
上面一段话是APUE中的解释,如果一个函数能够安全的同时被多个线程调用而得到正确的结果,那么,我们说这个函数是线程安全的。所谓安全,一切可能导致结果不正确的因素都是不安全的调用。
线程安全,是针对多线程而言的。那么和可重入联系起来,我们可以断定,可重入函数必定是线程安全的,但是线程安全的,不一定是可重入的。不可重入函数,函数调用结果不具有可再现性,可以通过互斥锁等机制,使之能安全的同时被多个线程调用,那么,这个不可重入函数就是转换成了线程安全。
线程安全,描述的是函数能同时被多个线程安全的调用,并不要求调用函数的结果具有可再现性。也就是说,多个线程同时调用该函数,允许出现互相影响的情况,这种情况的出现需要某些机制比如互斥锁来支持,使之安全。
版权申明:
转载文章请注明原文出处http://blog.csdn.net/feiyinzilgd/archive/2010/08/14/5811157.aspx
并请联系谭海燕本人或者前往谭海燕个人主页留言