線程安全函數是什么


什么是線程安全?
        一個函數被多個並發線程反復調用時,它會一直產生正確的結果,則該函數是線程安全函數。
那么什么又是可重入函數?
        當一個函數在被一個線程調用時,可以允許被其他線程再調用。即兩個函數“同時”發生。則該函數是可重入函數。
 
所以,顯而易見,如果一個函數是可重入的,那么它肯定是線程安全的。但反之未然,一個函數是線程安全的,卻未必是可重入的。比如我們在一個函數中調用到了一個全局變量NUM用來標記某一東西的數量。學個操作系統的同學都知道,如果我們在修改它的值的時候發生了中斷,兩一個函數又對他進行了修改,此時該變量的值會出錯。這種函數就是線程不安全函數。他是屬於沒有保護共享變量的線程不安全函數。在單線程時運行毫無問題,但一旦放到多線程中就容易出bug。但如果我們在修改這個全局變量NUM前對他進行加鎖,再操作完后再進行解鎖。這樣即使有兩個線程在調用這個函數,其結果也不會出問題。此時,這個函數就是線程安全函數。但他依舊不是可重入函數。因為他不能保證兩個函數“同時”運行,必須等待解鎖后才能運行。而我們在平時開發中應該盡量編寫可重入的函數。 如下圖:
 
 
線程不安全函數主要分為以下四大類:

第一類:不保護共享變量的函數,

a, 函數中訪問全局變量和堆。

共享變量在多線程中是共享數據比如全局變量和堆,如果不保護共享變量,多線程時會出bug。

可以通過同步機制來保護共享數據,比如加鎖。

第二類:函數中分配,重新分配釋放全局資源。

與上面第一點基本相同,通過加鎖可解決

第三類:返回指向靜態變量的指針的函數,函數中通過句柄和指針的不直接訪問。

比如,我們要計算a,b兩個變量的和,於是將a,b的指針傳入某一個函數,然而此時可能有另一個線程改變了a,b的值,此時在函數中我們通過地址取到的兩個數的值已經改變了,所以計算出的結果也就是錯的了。

又比如某些函數(如gethostbyname)將計算結果放在靜態結構中,並返回一個指向這個結構的指針。在多線程中一個線程調用的結構可能被另一個線程覆蓋。可以通過重寫函數和加鎖拷貝技術來消除。加鎖拷貝技術指在每個位置對互斥鎖加鎖,調用線程不安全函數,動態的為結果分配存儲器,拷貝函數返回的結構,然后解鎖。

第四類:調用線程不安全函數
常見的系統線程不安全函數:

線程不安全函數 線程不安全 類 unix線程安全版本
rand 2 rand_r
strtok 2 strtok_r
asctime 3 asctime_r
ctime 3 ctime_r
gethostbyaddr 3 gethostbyaddr_r
geyhostbyname 3 gethostbyname_r
inet_ntoa 3
 
localtime 3 localtime_r

UNIX環境高級編程列出 POSIX.1規范中的非線程安全的函數:

 

asctime ecvt gethostent getutxline putc_unlocked
basename encrypt getlogin gmtime putchar_unlocked
catgets endgrent getnetbyaddr hcreate putenv
crypt endpwent getnetbyname hdestroy pututxline
ctime endutxent getopt hsearch rand
dbm_clearerr fcvt getprotobyname inet_ntoa readdir
dbm_close ftw getprotobynumber L64a setenv
dbm_delete getcvt 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 lrand48 strerror
dbm_open getgrent getservbyname mrand48 strtok
dbm_store getgrgid getservbyport nftw ttyname
dirname getgrnam getservent nl_langinfo unsetenv
dlerror gethostbyaddr getutxent ptsname wcstombs
drand48 gethostbyname getutxid ptsname ectomb

目前大部分上述函數目前已經有了對應的線程安全版本的實現,例如:針對 getpwnam的 getpwnam_r(),( 這里的 _r表示可重入 (reentrant),如前所述,可重入的函數都是線程安全的)。在多線程軟件開發中,如果需要使用到上所述函數,應優先使用它們對應的線程安全版本。

 

因此在編寫線程安全函數時,要注意兩點:

  • 1, 減少對臨界資源的依賴,盡量避免訪問全局變量,靜態變量或其它共享資源,如果必須要使用共享資源,所有使用到的地方必須要進行互斥鎖 (Mutex) 保護;
  • 2, 線程安全的函數所調用到的函數也應該是線程安全的,如果所調用的函數不是線程安全的,那么這些函數也必須被互斥鎖 (Mutex) 保護;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM