[單刷APUE系列]第十二章——線程控制

線程屬性

在前一章中,都是使用的函數默認的屬性來賦予線程,可是pthread容許咱們經過設置對象關聯的不一樣屬性來細調線程和同步對象的行爲。而管理這些屬性的函數基本都是形式相同的。安全

  1. 線程和線程屬性關聯、互斥量和互斥量屬性關聯,一個屬性對象能夠表明多個屬性多線程

  2. 有一個初始化函數,而且能夠將屬性設置爲默認值異步

  3. 有一個析構函數,可以銷燬屬性對象而且回收資源ide

  4. 每一個屬性都有一個從屬性對象中獲取屬性值的函數函數

  5. 每一個屬性都有一個設置屬性值的函數測試

還記得咱們在上一章中使用pthread_create函數的時候,傳入的是null指針,函數就會使用默認值來設置線程,可是咱們也可使用pthread_attr_t結構體修改線程默認屬性。操作系統

int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);

就和以前的線程構造析構函數同樣,只不過產生的是線程屬性對象而已。
在前面內容中講到過度離線程的概念,若是咱們隊線程的終止狀態不感興趣的話,就可使用分離線程讓操做系統來對線程進行回收。可是,若是咱們是在建立線程的時候就須要分離線程,這就須要咱們使用pthread_attr_setdetachstate函數將其線程屬性設置爲PTHREAD_CREATE_DETACHED或者PTHREAD_CREATE_JOINABLE兩種值,也就是分離和等待。線程

int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

SUS標準除了POSIX標準規定的之外,還有一些另外的屬性,目前如今的Unix系統基本都是遵循這個標準的,因此線程棧也是一個重要屬性。在編譯階段使用_POSIX_THREAD_ATTR_STACKADDR_POSIX_THREAD_ATTR_STACKSIZE符號來檢查是否支持這兩個線程棧屬性,固然就像前面講過的同樣,咱們也可使用sysconf函數運行時檢查。通常來講,這編譯時檢查和運行時檢查都是必須的,由於你不知道是否會出現跨平臺編譯。
在蘋果系統平臺下,並無發現存在pthread_attr_getstackpthread_attr_setstack函數,蘋果系統將其分拆成了兩個函數,也就是總共四個函數指針

int pthread_attr_getstackaddr(const pthread_attr_t *restrict attr, void **restrict stackaddr);
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr, size_t *restrict stacksize);

int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

進程咱們知道是有一個棧的,並且進程的虛擬內存地址空間是固定的,因此大小徹底無所謂,可是全部線程共享着同一個進程的地址空間,因此,若是線程棧累計大小超過了可用空間,就會致使溢出。
若是線程棧的虛地址空間用完了,可使用malloc或者mmap來分配空間,而且使用上面的函數改變新建線程的棧位置,stackaddr參數指定的是棧的最低內存地址.
線程屬性guardsize控制着線程棧末尾後用於避免棧溢出的擴展內存大小。這個屬性默認值是根據Unix具體實現的。能夠把guardsize線程屬性設置爲0,不容許屬性的這種特徵行爲發生,這就會致使警惕緩衝區不存在。一樣,若是修改了stackaddr,系統就會認爲,咱們將本身管理棧,從而會致使警惕緩衝區無效。可是蘋果系統沒有提供這個函數,應該是默認已經設置了一個默認值,或者說根本就沒有設置警惕緩衝區。rest

同步屬性

除了線程屬性外,線程同步對象也有屬性,就好比說各類鎖

互斥量屬性

互斥量屬性使用pthread_mutexattr_t結構體,在前面的章節中,是使用PTHREAD_MUTEX_INITIALIZER常量或者用指向互斥量屬性結構的空指針做爲參數調用pthread_mutex_init函數。系統提供了相應的接口用於初始化。

int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);

這兩個函數就是初始化和反初始化函數,其中須要注意的就是:進程共享屬性、健壯屬性、以及類型屬性。
進程中,咱們知道,多個線程能夠訪問同一個資源,可是進程訪問同一個資源就須要設置PTHREAD_PROCESS_PRIVATE,或者說是進程共享互斥量屬性。
Unix環境實際上有一種機制:容許獨立的進程把同一個內存數據塊映射到一個公共的地址空間中,而後多個進程就能訪問共享的數據了,若是進程共享互斥量設置爲PTHREAD_PROCESS_SHARED,多個進程彼此共享的內存數據塊分配額互斥量就能夠用於進程同步。

int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);

互斥量健壯屬性和多個進程間共享的互斥量有關。這意味着,當持有互斥量的地址終止時,須要解決互斥量狀態恢復問題

int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict attr, int *restrict robust);
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr, int robust);

健壯性只有兩種狀況,PTHREAD_MUTEX_STALLED這意味着進程終止時沒有任何動做會採用,就可能致使等待着這個互斥量的其餘進程處於等待狀態。PHTREAD_MUTEX_ROBUST則是會對這個互斥量解鎖。
很是遺憾的是,蘋果沒有對以上兩種類型作出接口和支持,因此咱們也只能看看了。
類型互斥量屬性控制着互斥量的鎖定特性。POSIX標準規定了4中類型:

  1. PTHREAD_MUTEX_NORMAL 標準互斥量類型,不對其做任何的錯誤檢查或者死鎖檢測

  2. PTHREAD_MUTEX_ERRORCHECK 提供了錯誤檢查的類型

  3. PTHREAD_MUTEX_RECURSIVE 此類型容許同一線程在互斥量解鎖以前對該互斥量進行屢次加鎖,而且維護了鎖的計數

  4. PTHREAD_MUTEX_DEFAULT 這個互斥量類型能夠提供默認的行爲和特性。操做系統在實現它的時候就是映射到其餘互斥量的一種

int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);

上面這兩個函數就是設置和獲取函數

讀寫鎖屬性

讀寫鎖和互斥量很是類似,因此屬性的函數也是基本差很少的,這裏就隨便的列舉一下

int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);

讀寫鎖惟一支持屬性就是進程共享屬性,他只有兩個可能值。

int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr, int *restrict pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);

PTHREAD_PROCESS_SHARED Any thread of any process that has access to the memory where the read/write lock resides can manipulate the lock.
PTHREAD_PROCESS_PRIVATE Only threads created within the same process as the thread that initialized the read/write lock can manipulate the lock.  This is the default value.

條件變量屬性

不用多說,先來兩個初始化反初始化函數

int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);

條件變量只有兩個屬性:進程共享屬性和時鐘屬性,可是好像蘋果沒有這兩個屬性。因此也就不講了。
原著中的屏障屬性,實際上在蘋果系統下沒有說明,因此也就略過了。

重入

因爲線程是並行執行的,若是在同一時間點調用同一個函數,則有可能致使衝突,而若是一個函數在同一時間點能夠被多個線程安全的調用,就稱該函數是線程安全的。在Unix系統中,若是函數是線程安全的,就會在<unistd.h>中定義符號_POSIX_THREAD_SAFE_FUNCTIONS,固然也可使用sysconf函數獲取限制。
對於非線程安全函數,系統會提供可替代的線程安全函數,這些函數只是在名字後面加上_r,表面是可重入函數。可是可重入不表明是異步安全的,由於信號處理函數在調用的時候可能會致使衝突。

線程指定數據

也成爲線程私有數據,如同字面同樣,線程將某些特定數據只容許自身查詢。雖然線程模型輕量級的共享數據方式很是方便,可是也有着諸多弊端,因此引入了線程私有數據,用於維護基於線程的數據。例如errno,進程的errno不能共享給全部線程,因此後來就從新重構了errno的實現方式。
可是咱們知道,線程能訪問進程全部地址空間,除了內核提供的寄存器等存儲,線程理論上來講不存在真正私有的線程數據。可是有一種機制約束也更加安全些。

int pthread_key_create(pthread_key_t *key, void (*destructor)(void *));

是否是很像鍵值對,沒錯,這就是鍵值對,建立的鍵儲存在keyp指向的內存單元中,鍵能夠被全部線程使用,可是每一個線程把這個鍵和不一樣的線程特定數據地址關聯。除了鍵之外還有一個析構函數,當線程退出時候,若是數據地址被置爲非空值,那麼析構函數就會被調用,咱們能夠看到,它就一個無類型指針。
線程一般使用malloc爲私有數據分配內存,析構函數就是釋放已經分配的內存,若是不作析構,則會致使內存泄露。
線程退出時,線程特定數據的析構函數將會按照順序被調用,當全部析構函數結束後,系統會檢查是否還有非空線程特定數據與鍵關聯,若是有,則再次調用析構函數。直到全部鍵爲空。固然,也有最大嘗試次數。

int pthread_key_delete(pthread_key_t key);

這個函數就是取消鍵和線程私有數據的關聯。

取消選項

除了上面的屬性外,實際上還有兩個線程屬性:可取消狀態和可取消類型,這兩個屬性影響pthread_cancel函數行爲。
可取消狀態屬性有兩個值,PTHREAD_CANCEL_ENABLEPTHREAD_CANCEL_DISABLE,線程能夠經過調用pthread_setcancelstate修改

int pthread_setcancelstate(int state, int *oldstate);

很是容易理解的函數,便可以用來查看舊狀態也能夠用於修改。
在前面章節pthread_cancel調用不會等待線程終止,而是等到一個取消檢查點,統一檢查狀態,線程啓動的時候默認爲PTHREAD_CANCEL_ENABLE也就是接收取消,而爲PTHREAD_CANCEL_DISABLE則不會殺死線程,只會阻塞這個請求,等狀態再次變爲接收而且到達下一個檢查點的時候統一處理。
若是一直沒有到達檢查點,可能會致使取消的延遲,因此也提供了一個函數用於生成本身的檢查點。

void pthread_testcancel(void);

在默認狀況下,取消是延遲的,可是能夠經過調用pthread_setcanceltype修改

int pthread_setcanceltype(int type, int *oldtype);

參數類型只有兩種,PTHREAD_CANCEL_DEFERREDPTHREAD_CANCEL_ASYNCHRONOUS也就是異步和延遲。異步取消時,線程能夠在任意時間撤銷而不是到檢查點。

線程和信號

在前面關於信號的章節,信號是基於進程的,而引入了線程以後,信號的處理就更加複雜了。每一個線程都有了本身的線程屏蔽字,可是信號的接收處理則是統一給進程的,當進程註冊了信號處理函數後,全部線程的信號處理都會改變,可是進程中的信號是發送給單個線程的,一個線程能夠修改撤銷另外一個線程的信號選擇,

int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);

這就是sigprocmask函數的pthread版本,也就是多線程版本。二者基本相同,不過pthread_sigmask則是工做在線程下。
爲了簡化信號處理,pthread提供了另外一個函數

int sigwait(const sigset_t *restrict set, int *restrict sig);

set參數指定了線程等待的信號集。返回的時候sig參數指向的內存將包含發送信號的數量。
這個函數的好處就在於簡化了信號處理,將異步產生的信號經過阻塞的方式同步處理。一樣的,因爲線程的信號接收,也有了新的kill函數

int pthread_kill(pthread_t thread, int sig);

If sig is 0, error checking is performed, but no signal is actually sent.sig能夠指定爲0來測試線程的存在。

線程和fork

咱們講過,當fork的時候,子進程會基本繼承父進程的全部內容,也就是繼承了全部互斥量、讀寫鎖和條件變量。可是因爲多線程的存在,fork後子進程必須清理鎖的狀態。
也許各位第一反應,是否是父子進程將會一樣是多線程,實際上不是這樣的,子進程只會包含父進程調用fork的線程的副本。因爲子進程繼承了鎖,可是卻沒有繼承佔有鎖的線程,因此須要清理鎖,可是殊不知道清理哪些。
實際上,POSIX.1規定,在fork和第一個exec之間,子進程只能調用異步信號安全的函數,也就是限制了子進程「作什麼」,而不是「如何清理」。

int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));

爲了確保清理鎖,進程能夠調用pthread_atfork函數註冊清理函數,parent函數是fork建立子進程以後、返回以前在父進程中調用的,這是對全部鎖進行解鎖。child函數則是在fork返回以前,在子進程中調用。實際上二者解鎖一樣的內容,可是在不一樣的進程中而已。

線程和I/O

多線程下全部線程共享一樣的描述符,因此須要新的IO函數,而最簡單的辦法就是將其原子操做化,這樣就不會出現IO的衝突。也就是pread和pwrite函數。這裏再也不贅述。

相關文章
相關標籤/搜索