可重入函數
數組
中斷一個可重入函數的執行,轉而執行另一個函數(通常爲信號處理程序,注意此時依然爲同一個線程
),
返回可重入函數執行不會出現錯誤。安全
可重入與異步信號安全等價(APUE 3 edition, 10.6 )
多線程
可重入函數除了使用本身棧上的變量之外不依賴於任何環境(包括 static),這樣的函數就是可重入的,
能夠容許有該函數的多個副本同時運行異步
線程安全:
函數
若是一個函數在相同的時間點能夠被多個線程
安全地調用,就稱該函數是線程安全的。spa
1) 若一個函數如同可重入函數同樣,也沒有使用靜態數據(全局變量或static局部變量),那麼該函數也是線程安全的。
2) 或使用了靜態數據,但採用了同步機制,保證線程的安全調用,那麼也是線程安全的。操作系統
可重入與線程安全的關係:
線程
-------------------------------[Figure 1]--------------------------------
指針
舉例說明:func是線程安全的,但不是可重入的:
1)線程安全是顯然的,第b步在任意時刻只有一個線程訪問。
2)假設進程執行完第a步,接收到一個信號,轉而執行信號處理程序,恰巧該信號處理程序中也調用了函數func,那麼就陷入了死鎖。
3) 固然能夠利用遞歸鎖,那麼func也是可重入的,可是遞歸鎖的使用可能會帶來其餘嚴重問題
rest
-------------------------------[Figure 2]--------------------------------
支持線程安全函數的操做系統實現會在<unistd.h>中定義符號_POSIX_THREAD_SAFE_FUNCTIONS,對POSIX.1中的一些非線程安全函數,
它會提供可替代的線程安全版本。下面列出這些函數的線程安全版本。
POSIX.1還提供了以線程安全的方式管理FILE對象的方法。可使用flockfile和ftrylockfile獲取給定FILE對象關聯的鎖。
這個鎖是遞歸的:當你佔有這把鎖的時候,仍是能夠再次獲取該鎖,並且不會致使死鎖。
int ftrylockfile(FILE *fp); void flockfile(FILE *fp); void funlockfile(FILE *fp);
使用線程特定數據理由:
須要基於線程來維護一些數據(好比,線程id)
但願可以提供一種機制能夠在多線程的環境下使用基於進程的接口。(好比,errno)
int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *)); void* pthread_getspecific(pthread_key_t key); int pthread_setspecific(pthread_key_t key, const void *value);
int pthread_key_delete(pthread_key_t *key);
注意:取消鍵與當前線程TSD的關聯關係(鍵與其餘線程TSD的關聯關係不變),但並不會激活與鍵關聯的析構函數,
須要應用程序本身利用free函數
使用TSD過程:
1. 啓動一個進程並建立了若干線程(A,B),其中一個線程(好比線程A),要申請線程私有數據,系統調用
pthread_key_creat在下圖所示的key結構數組中找到第一個未用的元素,並把它的鍵(0)返回給調用者
2. 線程a經過pthrea_getspecific調用得到線程a的pkey[0]值,返回的是一個空指針NULL,
3. 用malloc在堆裏分配一段空間,使用pthread_setspecific調用將pkey[0]指向剛纔分配的內存區域。
4. 若線程b若使用相同的鍵,線程B只須要重複第三步。最終結果以下Figure 3所示。
------------------------------------------[Figure 3]----------------------------------------
須要確保分配的鍵並非因爲在初始化階段的競爭而發生變化:
int pthread_once(pthread_once_t *initflag, void (*initfn)(void));
struct tsd { pthread_t tid; }; pthread_key_t key; pthread_once_t create_done = PTHREAD_ONCE_INIT; void destructor(void* arg){ struct tsd* tsdptr = (struct tsd*)arg; free(tsdptr); } void key_create(void){ pthread_key_create(&key, destructor); } void key_init(){ struct tsd *tsdptr = (struct tsd*)malloc(sizeof(struct tsd)); tsdptr->tid = pthread_self(); pthread_setspecific(key, tsdptr); } void* p_fun(void* arg){ pthread_once(&create_done, key_create); key_init(); return (void*)0; }
1. 每一個線程都有本身的信號屏蔽字,可是信號的處理是進程中全部線程共享的.
2. 進程中的信號是傳遞到單個線程的,線程中必須使用pthread_sigmask設置信號屏蔽字.
3. 若是一個信號與硬件故障相關,該信號通常被髮送到引發該事件的線程中,不然信號被髮送到任意一個線程
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset); int pthread_kill(pthread_t thread, int sig);
[pthread_xxx的函數通常返回錯誤碼,而不是設置errno]
注意,用sigprocmask修改的是進程(主線程)的信號屏障字,而不是本線程的信號屏蔽字
int sigwait(const sigset_t * restrict set, int * restrict signop); signop爲當前接受到的信號[APUE 3 edition 中文版的解釋錯誤]
1. set爲等待的信號或信號集
2. 若信號集中的某些信號在sigwait調用時處於掛起狀態,那麼sigwait將無阻塞地返回,且移除這些掛起信號,若爲排隊信號,一次移除一個
3. 線程在調用sigwait以前,必須阻塞等待的信號(避免出現窗口期)。sigwait函數會原子地取消信號集的阻塞狀態,直到信號到來
4. 任意信號均可以喚醒調用sigwait的線程(和進程的sigsuspend很不一樣)
5. 使用sigwait的好處在於它能夠簡化信號處理,利用一個或多個線程專門處理信號,從而將異步產生的信號以同步方式處理
---5.1 假設專用線程A處理SIGINT,SIGQUIT信號, 在建立線程A以前,主線程首先屏蔽這兩個信號
---5.2 線程A建立以後,繼承主線程的信號屏蔽字,線程A調用sigwait函數
---5.3 sigwait取消對SIGINT與SIGQUIT的屏蔽,接着線程A阻塞,等待任意信號的到來
---5.4 當一個信號到來時,喚醒線程A,在線程A的正常的上下文中調用信號的處理程序
6. 若多個線程在sigwait的調用中由於同一個信號而阻塞,那麼在信號傳遞的時候,就只有一個線程從中返回
7. 若一個信號被捕獲(使用sigaction創建了一個信號處理程序),而一個線程正在sigwait調用中等待同一個信號,那麼不一樣操做系統的處理方式不一樣,因此儘可能避免
void* p_fun(void* arg){ sigset_t sigs; sigemptyset(&sigs); sigaddset(&sigs, (int)arg); int sig; for(;;){ if (sigwait(&sigs, &sig) != 0){ fprintf(stderr, "sigwait err"); return (void*)-1; } if (sig == SIGINT){ printf("tid : %ld receive SIGINT.\n", pthread_self()); } if (sig == SIGTSTP){ printf("tid : %ld receive SIGTSTP.\n", pthread_self()); } } return (void*)0; }
int main(){ sigset_t sigs; sigemptyset(&sigs); sigaddset(&sigs, SIGINT); sigaddset(&sigs, SIGTSTP); sigprocmask(SIG_SETMASK, &sigs, NULL); pthread_t tid[2]; if (pthread_create(&tid[0], NULL, p_fun, (void*)SIGINT) != 0){ fprintf(stderr, "pthread_create err"); exit(-1); } if (pthread_create(&tid[1], NULL, p_fun, (void*)SIGTSTP) != 0){ fprintf(stderr, "pthread_create err"); exit(-1); } for(;;){ /*main proccess*/ } exit(0); }
int pthread_kill(pthread_t tid, int signo);
能夠傳一個0值來檢查線程tid是否存在,相似kill檢查進程的存在性
鬧鐘定時器是進程資源,全部線程共享相同的鬧鐘
在子進程內部,只存在一個線程,以下圖所示,子進程中只有線程T3,由於在父進程中由線程T3建立了子進程。
因此互斥量M1,M2在子進程中就沒法解鎖。
``
不一致解決方法:
1. 若是子進程從fork返回之後立刻調用其中一個exec函數,能夠避免這種不一致,在fork返回和子進程調用其中一個exec函數之間,
子進程只能調用異步信號安全的函數
2. 調用pthread_atfork函數創建fork處理程序
int pthread_atfork()(void (*prepare)(void),void (*parent)(void),void (*child)(void));
------2.1 prepare由父進程在fork建立子進程前調用,prepare的任務是獲取父進程定義的全部鎖
------2.2 parent是在fork建立了子進程之後,在fork返回以前在父進程環境中調用的,parent的任務是對prepare得到的鎖進行解鎖
------2.3 child在fork返回以前在子進程環境中調用,與parent同樣,child必須釋放prepare處理程序得到的全部鎖
------2.4 能夠屢次調用pthread_atfork從而設置多套fork處理程序。parent和child是以他們註冊時的順序進行調用,
----------prepare的調用順序與他們註冊時的順序相反,好比若模塊A調用模塊B,每一個模塊都有本身的一套鎖。
----------那麼模塊B必須在模塊A以前設置它的pthread_atfork函數