《Programming with POSIX Threads》筆記

Ch 1 Introduction

異步(Asynchronous):如果兩個操作可以獨立進行,稱它們是異步的。 併發(Concurrency):看起來同時執行,但其實是順序執行。如多個線程或進程在單個CPU上的行為。 並行(Parallelism):真正同時執行。如多個線程或進程在多個CPU上的行為。 線程安全(Thread-safe):代碼能從多個線程被調用而不破壞結果。 可重入(Reentrant):代碼被多次調用產生的結果是可預期的。 值得注意的是,reentrant和thread-safe是兩個概念,它們既不充分也不必要。但非線程安全的函數一定是不可重入的。線程安全並不要求效率高,多數已有函數可以通過pthread中的mutex, condition variable或者TLS做到線程安全。可重入本質上要去除對靜態數據的訪問,更理想地,避免線程間同步的依賴。

pthread引入了新的方法來返回錯誤,而不是用傳統的errno。在傳統POSIX函數中,錯誤時返回-1,errno代表錯誤碼。pthread中成功返回0,否則返回錯誤碼。注意沒有perror()函數,而要用stderror()來得到錯誤描述。比較特殊的是,pthread_getspecific()為了效率不返回錯誤,如果key非法,它會返回NULL。

Ch 2 Threads

初始線程,也就是main函數所在線程比較特殊,它返回會同時銷燬其它線程,不會等它們結束。除非調用pthread_exit()退出。在主線程中調用pthread_exit() ,讓進程在所有線程都結束後才退出。Detach一個線程就是通知系統這個線程一旦結束,相應資源可以被回收。調用pthread_join()同時也會detach指定線程。如果多個線程要知道某個線程的退出情況,那麼應該用condition variable而不是pthread_join()。

當線程結束時,如果這時已經被detach了,那麼該線程會被馬上回收。如果創建的線程不需要join,那麼最好用detachstate屬性(PTHREAD_CREATE_DETACHED)。如果等待線程結束,結束線程可能在pthread_join()返回前就被回收了,所以線程的返回值不能是stack裡的。如果不滿足需求,可以自己設計返回值的傳遞機制。pthread_join()只是為了方便,不是強制的。

Ch 3 Synchronization

不變式(Invariants)是指程序做的假設,尤其指幾組變量之間的關係。 臨界區(Critical section),也稱為serial regions,是會影響共享數據的代碼。 判斷條件,或稱謂詞(Predicates),是描述代碼所需invariants狀態的邏輯表達式。 找Critical sections比找data invariants更容易,但critical section始終能和data invariant互轉。首選是同步數據而非代碼。

永遠不要拷貝mutex,因為使用一個mutex的拷貝是未定義的。但mutex的指針是可以傳遞拷貝的。當調用者線程已經獲得鎖時不能再次對mutex上鎖。這樣做的結果可能是返回錯誤,也可能是self-deadlock。如果用了pthread_mutex_trylock(),那麼保證只有在它返回成功時才調用pthread_mutex_unlock()。sched_yield()會把處理器給下一個ready的thread運行,如果沒有的話立即返回。在sleep和yield之前一般要把mutex給解鎖。

mutex的保護範圍要在達到互斥前提下儘可能小。mutex的設計原則:1. 鎖有開銷,因此鎖少的代碼往往比鎖多的運行地快。2. 但如果用大鎖會導致線程經常會因為自己不會訪問的數據的鎖而等待,這時要考慮拆分成若干小鎖。3. 前兩者是矛盾的。在一個複雜系統裡一般要經過實現才能達到平衡。通常做法是從大鎖開始,然後看嚴重的競爭發生在哪再換成小鎖。

哪怕在單個處理器上也會因為搶佔(preemption)出現死鎖。兩種通用做法:Fixed locking hierachy和Try and back off。前者勝在高效,後者比較方便靈活。相對地,解鎖可以以任何順序,不會產生死鎖。 有一種情況稱為"overlapping hierarchy",又稱為"lock chain" ,這種用法在遍歷樹或鏈表時很有用。這種情況下如果用Try and back off方案就需要逆序來解鎖。逆序解鎖減少了其它線程需要做back off的機率。

條件變量(condition variable)本身的存在形式也是共享的,所以需要一個mutex來保護。pthread_cond_wait()包含unlock和wait操作,當wait返回時會重新lock。因此,condition variable的wait返回時mutex始終是lock的。pthread保證unlock和wait是原子的。因為如果不是的話會導致A線程unlock後,B線程signal,A錯過signal(因為還沒開始wait),然後線程A再開始wait。

一個condition variable應該只對應一個predicate(不是強制的)。如果你想讓一個condition variable在多個predicate間共享,就更有可能發生死鎖或競爭。基本原則是:1. 當在多個predicate共享condition variable時,你喚醒時得用broadcast,而不是signal。2. signal比broadcast更高效。predicate和condition variable都是共享數據,它們一般被同一個mutex保護。每一個condition variable都有一個對應的mutex和一個predicate condition。和mutex類似,永遠不要拷貝condition variable。如果是靜態初始化的condition variable不需要用pthread_cond_destroy()來銷燬。

多個線程如果等待在同一個condition variable上,就要用同一個mutex。pthread不允許兩個線程同時等在同一個condition variable上,卻指定不同的mutex。一個condition variable在給定時刻只能對應一個mutex,而一個mutex可以對應多個condition variable。在lock和wait間檢查predicate非常重要。當pthread_cond_signal/broadcast()調用時,如果沒有等待者,那麼不會觸發任何事。當另一線程pthread_cond_wait()調用,也不會收到之前的signal。wait前一般步驟是:lock() ->check predicate -> wait()。 當線程醒來時檢查predicate也很重要。condition variable的等待永遠應該在loop中,以防program error, multiprocessor race和spurious wakeup。不要假定condition variable的wait返回時predicate就為真,即使有且只有另一線程將predicate置真並signal。主要有幾種原因:Intercepted wakeup, Loose predicate.和Spurious wakeup。

當signal/broadcast時,可以不用lock住mutex。這樣的好處是在許多系統上效率更高。因為當被喚醒線程醒來時,它會先上鎖。如果已經被髮signal的線程lock的話,它會block住。但也有壞處。如果signal時mutex沒有lock,一個低優先級的線程可以先lock住。如果被喚醒線程是高優先級的話,就會造成高優先級的線程延遲執行。也有可能造成intercepted wakeup。

關於線程間的內存可見性(memory visibility),pthread會保證幾條基本規則:當線程在pthread_create(), unlock mutex,terminate和signal/broadcast時,它所看到的內存數據能被之後創建或醒來的線程看到。時刻要記得現在的存儲體系下,每個CPU有自己的cache。當write指令執行完時,只是放在cache中。而何時cache同步到主存是由系統決定的,程序員無法預測。哪怕是在同一個線程中,兩次write的內容和它們flush到主存的順序都是不一定的。確保memory coherence或read/write ordering會極大地犧牲性能。所以很多現在計算機並不保證,除非程序員用memory barrier指定。memory barrier保證在它之前發起的內存訪問在它之後發起的內存訪問結束之前結束。注意它不是cache flush的命令。

每種計算機都有內存粒度。即使CPU以8 bit為單位讀取,內存可能以32或64 bit的內存單元傳輸。也就是說兩個線程同時寫一個32 bit的其中兩個byte,會導致後寫的那個把前面的覆蓋掉。另一種情況是當數據不是對齊的話,有可能傳輸的過程就不是原子的。這種情況下兩個線程各寫一部分的話,結果就是"word tearing"。也就是結果一部分是線程A的,一部分是線程B的。

Ch 4: A few ways to use threads

用多線程解決問題的模式可以有很多變體,其主要的幾種有Pipeline(Assembly line), Work crew和Client/Server。

Ch 5: Advanced threaded programming

Pthread早期不支持靜態初始化mutex,為了讓一塊初始化代碼只執行一次,有了pthread_once()。

線程的取消只是退出的請求,並不是強行停止。Cancellation有幾種模式狀態,默認為deferred。取消線程是異步的,也就是說返回時目標線程不一定已經被取消了。只是取消請求在pending過程中。如果要確認線程是否真的取消了,必須要用pthread_join()。

避免用asynchrounous cancellation,因為你很難用對。Asynchrounous cancellation可以在任意硬件指令時發生。當asynchrounous cancellation enable時不允許調用任何會申請資源的函數,因為你不知道線程取消的時候資源分配了還是沒分配。事實上,這時只能調用async-cancel safe的函數。總之,用前請三思。

當寫庫代碼時,設計需考慮deferred cancellation。當線程不能被取消的時候就得禁掉,並始終在cancellation point處用cleanup handler。每個線程都有個cleanup handler的棧,用pthread_cleanup_push/pop來增加減少其內容。當線程被取消或者它自己調用pthread_exit()時,會從這個棧中拿handler執行,當所有handler執行完,線程結束。pthread_cleanup_push/pop只能在同一函數中使用,因為它們可能是由宏實現的。pthread_cleanup_pop的參數是非0的話就是說在cancellation沒發生的時候也會調用handler。當一個contractor線程把任務分配到多個subcontractor線程中(如進行並行算法時)需要在contractor線程的cleanup handler中對所有subcontractor線程進行cancel和detach。這樣,當這個contractor被cancel時,這些subcontractor線程的資源可以及時被釋放。

有時需要每個線程要有自己的私有變量。當你在設計全新的接口時,較好的方案是讓caller分配這個線程私有區域,然後傳給線程。但現實是有時你不能影響現有的接口。這時就可以用TSD(Thread-specific Data)。key被創建來指示私有數據,這個key每個線程都一樣,但在不同線程指示不同的值。創建時要保證同一個pthread_key_t只能調用一次pthread_key_create(),否則就會創建兩個不同的key,而前一個會永遠丟失。最好的辦法就是pthread_once()保證只創建一次。當key被銷燬程序員需要負責在每個線程中釋放這個key關聯的內存。只有當確定沒有線程有某個key對應的值時再刪除key,否則就永遠不要刪除。

Thread-specific data為NULL有特殊含義,不要輕易將之設成NULL。當線程結束時,pthread會把這個值設為NULL,然後調用key的destructor函數。如果key對應的value是一個heap區域的地址,需要要在desturctor釋放,就得用destructor的參數來傳地址,而不是通過pthread_getspecifc(),因為它在調用destructor前就已經是NULL了。如果這個key的value已經是NULL的話,pthread不會調用key的destructor。事實上,NULL代表該線程中“這個key不再有值”。TSD destructor函數只有線程結束時才被調用,並且它的調用順序是不確定的,因此destructor函數要相互獨立。TSD destructor中不要為value已經被銷燬的key設置新value。但有時候會間接出現這種情況(如調用fprintf)。這樣在destructor全被調用後系統又得重新檢查一遍。理論上這可以構成死循環。_PTHREAD_DESTRUCTOR_ITERATIONS定義了系統重新檢查的次數。超過次數後就放棄執行destructor,如果value為heap中的值,可以會引起內存洩露。

Realtime scheduling中的幾種調度策略包括SCHED_FIFO, SCHED_RR, SCHED_OTHER。注意SCHED_OTHER的行為不是可移植的。當在屬性對象中設置scheduling policy或policy attributes時,也必須設置inheritsched attribute。

Contention scope分兩種:system contention scope與進程外搶CPU,process contention scope在進程內搶CPU。system contention scope下線程一般在kernel中有對應項,而process contention scope下沒有。前者更加可控和可預測,後者更加有效率。Allocation domain是在系統中線程搶奪的CPU集合。在單處理系統中,一個allocation domain包含一個CPU,但仍可以有多個allocation domain。

Realtime scheduling的問題在於你設的高優先級線程會搶佔其它高優先級線程的資源。另一個問題是realtime priority線程未必比其它線程快,因為在preemption checks時會有更多開銷(尤其在多CPU系統中)。更嚴重的問題是priority inversion,即一個低優先級線程阻止高優先級線程運行。比如一個低優先級線程佔有資源,被高優先級線程搶佔,但被block在同一個資源上。這時有一個優先級介於它們之間的線程把低優先級線程搶佔了,於是低優先級無法釋放資源,導致高優先級線程無法運行。Pthread提供了priority ceiling和priority inheritance機制來解決priority inversion。最後一個問題是priority scheduling不是完全可移植的。如果非要用priority scheduling,傾向process contention scope,SCHED_RR和低優先級。除非你的代碼真的需要priority scheduling,不要用它。

Pthreads會保證“一個線程中調用的系統服務不會阻塞其它線程”。“Pthread thread”和"Processor"之間有層抽象,稱為"kernel entity"。在一些系統上,它是process,一些是LWP。Pthread線程不是kernel的調度單位,kernel entity才對應具體系統上的調度單位。在實現上,有幾種模型:Many-to-one, One-to-one和Many-to-few。如Linux下的線程模型都是One-to-one的。

Ch 6 POSIX adjusts to threads

避免在有線程的程序中用fork,除非你打算fork完了立馬用exec。進程調用fork時,pthread規定在子進程中只有那個調用fork的線程存在。但是,其它線程的狀態還是保持fork時的狀態。對於那些其它線程,pthread不是調用pthread_exit()或是cancel,而是直接停止,意味著它們的TSD destructor和cleanup handler都不會被調用,因此可能發生memory leak,特別是放在TSD中的heap memory。注意如果mutex在fork時lock,那麼在child process中仍是lock的,而locked mutex被lock它的線程所有,意味著只有在lock mutex和call fork是同一個線程時這個mutex才能被unlock。因此,如果其它線程在fork時lock了 mutex,那麼在子進程中這個mutex和它保護的數據就永遠不能訪問了。Fork handler的目的就是讓有線程的系統在fork時能保護同步狀態和數據一致性。如果你的系統中用了mutex且沒建立fork handler,那fork後的子進程多半不會工作正常。該函數類似於atexit(),它提供三個handler:prepare fork handler,parent fork handler和child fork handler。

exec()會結束所有線程(除了調用者線程),cleanup handler和TSD destructor不會被調用。所有的synchronization objects都會銷燬,除非是pshared mutex/condition variable。如果有pshared mutex最好unlock掉。exit()結束進程,pthread_exit()結束線程。main()返回和exit()的效果是一樣的。如果主線程沒用了,可以用pthread_exit()讓其它線程繼續運行。雖然fork是signal-catching functions("async-signal safe" functions),但fork會調用其它未必安全的函數,所以不要在signal-caching function中用fork。

Pthread規定stdin函數是線程安全的。但在一些情況下,我們希望多個讀或寫操作之間不能被中斷。這時可以用flockfile()和funlockfile()。pthread建議先lock input再lock output,從而避免stdin裡的死鎖。getchar/putchar/getc/putc這些函數都是用宏實現的,pthread要求它們都加鎖來防止破壞stdio緩衝區。但有時lock, unlock的代價甚至超過了拷貝字符的代價,於是pthread定義了新的函數getc_unlocked/putc_unlocked/getchar_unlocked/putchar_unlocked,它們必須用flockfile()和funlockfile()保護起來。當讀寫單個字符時,建議用有鎖版。當讀寫一串字符時,可以考慮用無鎖版+flock。很多函數可以在不變接口的情況下變成thread-safe,如malloc,但有些情況下在callee中做同步還不夠。比如返回static buffer的函數,或是在多次調用都會訪問靜態上下文的情況。在這些情況下,pthread定義了已有函數的線程安全變體,以 “_r”結尾。它們將上下文移出庫,讓caller控制。

Pthread中對於signal的處理一直是比較另人糾結的。因此,最好不要thread和signal混用。如果要用,可能的話在主線程用pthread_sigmask來mask signal,然後在專用線程中用sigwait以同步方式處理異步signal(man pthread_sigmask有例子)。要等待的signal需要在sigwait前被mask,最好在主程序中mask,因為所有的線程會繼承創建者的signal mask。這樣就保證信號不會發送到非等待sigwait的其它線程中。注意signal只發送一次,兩個線程同時sigwait,只有一個拿到。如果非要用sigaction來處理同步signal(如SIGSEGV),要格外小心,在signal-handler裡儘量少做事情。

所有的signal action都是process-wide的,因此如果兩個線程同時用sigaction,會出現線程A的signal被線程B的handler處理,但又是在線程A的上下文中。一般signal會被髮送到任一線程中,也就是說SIGCHLD不一定會被髮送到創建子進程的線程中。類似地,kill產生的signal可以發給任一線程。而同步的“hardware context”signal,如SIGFPE, SIGSEGV, SIGTRAP只會被髮送到引起信號的線程。你不能通過SIGKILL或SIGSTOP殺掉或者停止一個線程。也就是說往任一線程發SIGKILL會殺掉整個進程。如果僅想殺線程可以用pthread_cancel()。類似地,SIGSTOP到某個線程會停止進程中的所有線程。對於程序員來說,最好不要在庫裡改signal action,signal action統一在主程序中改。

每個線程能設私有的信號掩碼(signal mask,用pthread_sigmask)。pthread沒有定義sigprocmask在多線程進程中的作用,因此如果想要移植性好的話最好不要用。另外,signal mask具有繼承性。

當pthread_kill()時,如果目標線程mask了該signal,它會標記等待執行。如果等待在sigwait上,會收到這個信號。如果兩者都沒有,當前的signal action被執行。

不能在signal-caching function中使用mutex,因為不知道這個signal前面是否有lock了。注意可能被signal打斷或者被suspend的函數中不要用mutex,包括顯式的和隱式的,後者如printf中對stdio的lock,malloc中的lock等。

Pthread增加了新的通知機制SIGEV_THREAD。新線程默認的屬性是PTHREAD_CREATE_DETACHED(指定PTHREAD_CREATE_JOINABLE是未定義的)。這個handler函數不一定是在新線程中執行,可能是在server thread中。SIGEV_THRAD和signal-caching function相較之下優點是可以用pthread的同步操作。

mutex/condition variable不能滿足所有需求,比如signal-catching function和等待異步事件的線程需要通信的需求。當然最好的方法是用sigwait,但很多老代碼已經用了signal-catching function。要從signal-catching function中喚醒一個線程,需要一個對於POSIX signal來說reentrant的機制(也就是async-signal safe)。而sem_post()是async-signal safe的,這意味著可以在signal-catching function中喚醒同個或者不同個進程中的其它線程。大多數情況下只有在signal-catching中喚醒其它線程時才用。和mutex/condition variable相比,semaphore有兩個特點:1. 沒有owner,任意線程可以release等待在semaphore上的線程。2.不依賴外部狀態。condition variable依賴於shared predicate和mutex。另外,POSIX semaphore不像其它函數,它是用errno來報告錯誤的。

Ch 7 "Real code"

將已有庫變成thread-safe,難點在於兩類函數:在一系列的調用中依賴於靜態存儲的函數,或返回static storage的指針的函數。一種方法是在subsystem加big mutex,但它可以解決synchronization race,但不能解決sequence race。這種方法適合於庫有內部的數據,而該數據對外部不可見,如malloc。big mutex的難點在於定義subsystem。接口函數之間可能相互調用,這時防止deadlock的一種方法是定義locked接口,另一方法是recursive lock。有persistent state的函數用big mutex還不夠,因為返回的指針指向static storage,一種方法是重新實現,讓這個函數從malloc中分配空間,然後caller將之放在TSD中,另一種方法是讓caller在真正使用完後再unlock。最好的方法是改變接口,使函數使用caller分配的buffer,caller可以自己決定什麼時候lock, unlock。如_r系函數。

如果沒有源代碼,要在多線程環境中使用一個非線程安全的庫的通用辦法有以下幾種:方法一是僅在一個server thread中使用該庫。方法二是在接口外加big mutex。這裡是external big lock(在接口函數外,前面是internal big lock)。這適合thread-safe interface而不是thread-safe implementation的情況。thread-safe interface指函數依賴於static state,但是返回的東西不受後面調用的影響,如malloc。然而它不適用於會block很長時間的庫,它會讓系統低效。方法三是用external state來擴展實現。像asctime()會返回static storage,可以用lock -> call the function -> copy the return value into a thread-safe buffer -> unlock -> return的流程處理。這個thread-safe buffer可以動態分配,也可以用TSD。或者改接口讓caller提供buffer。對於一些在sequence of calls保持persistent state的函數就比較難包裝,很多時候需要創新。

Ch 8 Hints to avoid debugging

不要依賴於"thread inertia"(它是thread race的一種特例)。新創建或unblock一個線程不一定會馬上執行。如果線程A創建線程B,並且優先級一樣,那A會繼續執行。也就是說,處理器的運行線程有更高的優先權。好的做法是在線程創建前把要線程要看到的東西準備好。新創建的線程依賴於創建者線程的臨時數據也不是好的做法。另外格外注意的是線程開始的順序也是不確定的。

Race是當多個線程同時試圖到達某處或拿到某樣資源。當寫多線程代碼時,需要假設在任一時刻,在任一條語句內,每個線程都會睡眠無限長時間。memory visibility保證在單個CPU上一個線程總能看到前面線程所作的改動。但不同的CPU上,沒有順序而言,要考慮最壞的可能。把scheduling policy高為SCHED_FIFO,然後設成最大的優先級也無濟於事,因為在多CPU中,其它線程不需要preemption就可以同時執行。Sequence races通常發生在當你假設了某些事件的順序,但代碼又沒有處理這些順序的時候。如readdir,它有內部靜態數據來記錄狀態,以保證一系列的相同調用可以工作。如果兩個線程同時用,會導致狀態錯亂。即使用mutex保護了共享數據也沒用。

Priority inversion是realtime priority scheduling中的獨有問題,至少需要三個運行在不同優先級的線程,並且共享資源。它是同步與優先級的矛盾所致,可以讓低優先級線無限讓高優先級線程等待。解決策略包括:1 避免realtime scheduling。2. 不同優先級的線程不允許用同個mutex。3. priority ceiling mutex或者priority inheritance。4. 避免調用可能會lock高優先級線程中不是你創建的mutex的函數。

理想的並行代碼是compute-bound的任務。理想的併發代碼是I/O-bound的任務。不是引入多線程就一定會有並行效果,有時可能更差。因為我們可能會把單線程的序列任務變成多線程的序列任務,如malloc,free中會有mutex。如果頻繁調用,線程可能會花很多時間等待。另外,I/O線程如果讀寫同一個文件,那自然就是序列化的。哪怕不是同一個文件,有些文件系統會給整個文件系統上鎖來保證cache。哪怕是不同disk,這也取決於I/O總線和硬盤控制器。

如果沒有sequence race,那big lock基本夠了,就像Xlib。但這不利於併發度,一個通用策略是為每個數據結構加個mutex。但mutex並非越多越好,多了之後不僅lock, unlock的開銷大了,還會因為lock mutex使所有CPU的相應cache被invalidate。還會停止一段物理地址中的總線行為。當你發現兩個數據結構通常放在一起用時,就可以合併它們的mutex。

現在計算機都不直接訪存而是使用cache,cache通常是以block為單位(如64, 128 bytes)。這意味著,當同樣一個block被多個CPU所cache,當一個CPU寫了其中一部分,其它的cache也得丟棄。cache的行為每個平臺都不一樣,需要根據其特性進程優化。保證沒有兩個線程會在performance-critical paralell loop中訪問同一個cache block。平臺無關的做法是以頁對齊,因為很少有cache會和頁一樣大。

Ch 9 POSIX threads mini-reference

POSIX定義了一些宏來讓代碼在編譯時判斷系統是否具有特定能力。POSIX以三種形式給出limit:1. POSIX Conformance Document 。2. limits.h中的編譯時符號常量。3. sysconf。

Ch 10 Future standardization

mutex分幾種:normal的mutex不會檢測deadlock錯誤。其它還有recursive和errorcheck的mutex。default可能會被映射到前面三種中的一種。不要在一個線程中lock,另一個線程中unlock(有這種需求的話請用semaphore)。normal mutex一般是最快的,但提供最少的錯誤檢查。recursive mutex主要用於轉換難以劃清同步界限的舊代碼。從可維護性和性能考慮應避免recursive mutex。errocheck mutex作為調試工具,要用的話需要重新編譯,它比一般的mutex要慢,因為要維護額外的狀態和做額外的檢查。

多數線程實現都會在線程棧加guard區域(如一個page)。它有兩個作用:1. 允許更大的值,防止overflow 2. 允許更小的值,減少線程數量很多時的開銷。

通過paralell I/O來提升性能的一個問題是file position是fd的屬性,是共享的。pread()和pwrite()讓seek和pread/pwrite操作綁定成為原子操作。這樣I/O請求可以同時執行,而且不用對fd加鎖。

POSIX.1j加了barrier, read/write lock, conditon variable wait lock, spinlock。spinlock是系統中最原始且最快的同步機制,它是平臺相關的。它和mutex的區別在於它在lock已經被其它線程搶佔的鎖時不會block,而是retry。一般用於多CPU時的fine-grained parallelism。它一般用於幾條指令級的快速的操作。spinlock等待的時候通常不應該大於context switch的時間。spin前綴的是進程間spinlock同步接口。pthread前綴的是線程間spinlock同步接口。它們的實現可以相同也可以不同。

pthread condition variable只支持絕對時間的timeout。這是因為condition variable的等待會受到spurious wake等影響。當時間沒到就被喚醒,如果是相對時間就很難檢查剩餘時間。但絕對時間有個缺點,就是如果遇到時鐘調整,那這個timeout不會相應調整。POSIX.1j中通過CLOCK_MONOTONIC解決了這個問題。它是一個單調的timer,不會受時鐘調整的影響。

pthread_abort()是fail-safe cancellation,它用來確保一個線程被立即結束。它不會運行cleanup handler,也不會有機會做其它清理工作。比如一個不能關掉的實時控制系統,某個線程不正常,而cancel又無法將之結束,就只能用abort。