深入理解可重入與線程安全
在多線程編程和信號處理過程中,經常會遇到可重入(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中的解釋,如果一個函數能夠安全的同時被多個線程調用而得到正確的結果,那麼,我們說這個函數是線程安全的。所謂安全,一切可能導致結果不正確的因素都是不安全的調用。
線程安全,是針對多線程而言的。那麼和可重入聯係起來,我們可以斷定,可重入函數必定是線程安全的,但是線程安全的,不一定是可重入的。不可重入函數,函數調用結果不具有可再現性,可以通過互斥鎖等機製,使之能安全的同時被多個線程調用,那麼,這個不可重入函數就是轉換成了線程安全。
線程安全,描述的是函數能同時被多個線程安全的調用,並不要求調用函數的結果具有可再現性。也就是說,多個線程同時調用該函數,允許出現互相影響的情況,這種情況的出現需要某些機製比如互斥鎖來支持,使之安全。
版權申明:
轉載文章請注明原文出處https://blog.csdn.net/feiyinzilgd/archive/2010/08/14/5811157.aspx
最後更新:2017-04-02 06:51:24