GB | BIG5
|
| 首頁 > 編程技術 > C/C++ > 正文 |
 |
| Solaris2.4 多線程編程指南4--操作系統編程 |
| 本文出自:BBS水木清華站 作者:Mccartney (coolcat) (2002-01-29 20:29:36) |
4. 操作系統編程 本章討論多線程編程如何和操作系統交互,操作系統作出什改變來支持多線 程。 進程--為多線程而做的改動 警告(alarm), 計數器(interval timer), 配置(profiling) 全局跳轉--setjmp(3C) 和longjmp(3C) 資源限制 LWP和調度類型 擴展傳統信號 I/O 問題 4.1進程--為多線程而做的改變 4.1.1復制父線程 fork(2) 用fork(2)和fork1(2)函數,你可以選擇復制所有的父線程到子線程,或者子 線程只有一個父線程???。 Fork()函數在子進程中復制地址空間和所有的線程(和LWP)。這很有用,例如, 如果子進程永遠不調用exec(2)但是用父進程地址空間的拷貝。 為了說明,考慮一個父進程中的線程--不是調用fork()的那個--給一個互斥鎖 加了鎖。這個互斥鎖被拷貝到子進程當中,但給互斥鎖解鎖的線程沒有被拷貝。所 以子進程中的任何試圖給互斥鎖加鎖的線程永久等待。為了避免這種情況,用fork() 復制進程中所有的線程。 注意,如果一個線程調用fork(),阻塞在一個可中斷的系統調用的線程將返回 EINTR。 Fork1(2) Fork1(2) 函數在子線程中復制完全的地址空間,但是僅僅復制調用fork1()的 線程。這在子進程在fork()之立即調用exec()時有用。在這種情況下,子進程不 需要復制調用fork(2)函數的那個線程以外的線程。 在調用fork1()和exec()之間不要調用任何庫函數--庫函數也許會使用一個由 多個線程操作的鎖。 *Fork(2)和fork1(2)的注意事項 對fork()和fork1(),在調用之使用全局聲明時要小心。 例如,如果一個線程順序地讀一個文件而另外一個線程成功地調用了fork(), 每一個進程都有了一個讀文件的線程。因為文件指針被兩個線程共享,父進程得到 一些數據,而子進程得到另外一些。 對fork()和fork1(),不要創建由父進程和子進程共同使用的鎖。這僅發生在 給鎖分配的內存是共享的情況下(用mmap(2)的MAP_SHARED聲明過)。 Vfork(2) Vfork(2)類似fork1(),只有調用線程被拷貝到子進程當中去。 注意,子進程中的線程在調用exec(2)之前不要改變內存。要記住vfork()將父 進程的地址空間交給子進程。父進程在子進程調用exec()或退出重新獲得地址空 間。子進程不改變父進程的狀態是非常重要的。 例如在vfork()和exec()之間創建一個新線程是危險的。 4.1.2執行文件和終止進程 exec(2)和exit(2) exec(2)和exit(2)調用和單線程的進程沒有什區別,只是它們破壞所有線程 的地址空間。兩個調用在執行資源(以及活動線程)被破壞前阻塞。 如果exec()重建一個進程,它創建一個LWP。進程從這個初始線程開始執行程序。 象平時一樣,如果初始線程返回,它調用exit()來破壞整個進程。 如果所有線程退出,進程用0值退出。 4.2 Alarms(鬧鐘???), Interval Timers(定時器), and Profiling(配置) 每個LWP有一個唯一的實時的定時器和一個綁定在LWP上的線程的鬧鐘。定時器 和鬧鐘在到時間時向線程發送信號。 每個LWP有一個虛擬時間或一個配置定時器,綁定在該LWP上的線程可以使用它 們。如果虛擬定時器到時間,它向擁有定時器的LWP發送信號SIGVTALRM或SIGPROF, 發送哪一個視情況而定。 你可以用profil(2)給每一個LWP進行預配置,給每個LWP私有的緩沖區或者一個 LWP共享的緩沖區。配置數據按LWP用戶時間的每一個時鐘單位更新。在創建LWP時配 置狀態被繼承。 4.3非本地跳轉--setjmp(3C)和longjmp(3C) setjmp()和longjmp()的使用范圍限制在一個線程裡,在大多數情況下是合適的。 然而,只有setjmp()和longjmp()在同一個線程裡,線程才能對一個信號執行longjmp()。 4.4資源限制 資源限制在整個進程內,每個線程都可以給進程增加資源。如果一個線程超過 了軟資源限制,它將發出相應的信號。進程內可用的資源總量可以由getrusage(3B) 獲得。 4.5 LWP和調度類型 Solaris 內核有3種進程調度類型。最高優先級的是實時(realtime RT)。其次 是系統(system)。系統調度類型不能在用戶進程中使用。最低優先級的是分時 (timeshare TS),它也是缺省類型。 調度類型在LWP內維護。如果一個進程被創建,初始LWP繼承父進程的調度類型和 優先級。如果有跟多的LWP被創建來運行非綁定線程,它們也繼承這些調度類型和優先 級。進程中的所有非綁定線程有相同的調度類型和優先級。 每個調度類型按照調度類型的配置優先級,把LWP的優先級映射到一個全體的分配 優先級。??? 綁定線程擁有和它們綁定的LWP相同的調度類型和優先級。進程中的每個綁定線程 有一個內核可以看到的調度類型和優先級。系統按照LWP來調度綁定線程。 調度類型用priocntl(2)來設置。前兩個參數的指定決定了是只有調用的LWP還是 一個或多個進程所有的LWP都被影響。第三個參數是一個指令,它可以是以下值之一。 ﹒ PC_GETCID--獲得指定類型的類型號和類型屬性 ﹒ PC_GETCLINFO--獲得指定類型的名稱和類型屬性 ﹒ PC_GETPARMS--獲得類型標識和進程中,LWP,或者一組進程的因類型而異 的調度參數 ﹒ PC_SETPARMS--設置類型標識和進程中,LWP,或者一組進程的因類型而異 的調度參數 用priocntl()僅限綁定線程。為了設置非綁定線程的優先級,使用thr_setprio(3T)。 4.5.1分時調度 分時調度將執行資源公平地分配給各進程。內核的其他部分可以在短時間內獨佔 處理器,而不會使用戶感到響應時間延長。 Priocntl(2)調用設置一個或多個線程的nice(2)級別。Priocntl()影響進程中所有 的分時類型的LWP的nice級別。普通擁護的nice()級別從0到20,而超級用戶的進程從 -20到20。值越小,級別越高。 分時LWP的分配優先級根據它的LWP的CPU使用率和它的nice()級別來確定。Nice() 級別指定了在進程內供分時調度器參考的相對優先級。LWP的nice()值越大,所得的執 行資源越少,但不會為0。一個執行的多的LWP會被賦予比執行的少的LWP更小的優先級。 4.5.2實時調度 實時類型可以被整個進程或進程內部的一個或多個線程來使用。這需要超級用戶 權限。與分時類型的nice(2)級別不同,標識為實時的LWP可以被獨立或聯合地分配優先 級。一個priocntl(2)調用影響進程中所有實時的LWP的屬性。 調度器總是分配最高優先級的實時LWP。如果一個高優先級的LWP可運行,它將打斷 低優先級的LWP。一個有先行權(preempt)的LWP被放置在該等級隊列的頭上。一個實 時(RT)的LWP保持控制處理器,直到被其他線程中斷時掛起,或者實時優先級改變。 RT類型的LWP對TS類型的進程有絕對的優先權。 一個新的LWP繼承父線程或LWP的調度類型。一個RT類型的LWP繼承其父親的時間片, 不管它是有限還是無限的。一個有有限時間片的LWP持續運行直到結束,阻塞(例如等 待一個I/O事件),被更高優先級的實時進程中斷,或者時間片用完。一個擁有無限時 間片的進程則不存在第四種情況(即時間片用完)。 4.5.3 LWP調度和線程綁定 ﹒ 線程庫自動調節緩沖池中LWP的數量來運行非綁定線程。其目標是: 避免程序因為缺少沒有阻塞的LWP而阻塞。 例如,如果可運行的非綁定線程比LWP多而所有的活動線程在內核中處無限等待 的阻塞狀態,進程不能繼續,知道一個等待的線程返回。 ﹒ 有效利用LWP 例如,如果線程庫給每個線程創建一個LWP,許多LWP通常處閑置狀態,而操作 系統反而被沒用的LWP耗盡資源。 要記住,LWP是按時間片運行的,而不是線程。這意味著如果只有一個LWP,則進程 內部沒有時間片--現成運行直到阻塞(通過線程間同步),被中斷,或者運行結束。 可以用thr_setprio(3T)來為線程分配優先級:只有在沒有高優先級的非綁定線程 可用時,LWP才會被分配給低優先級的線程。當然,綁定線程不會參與這種競爭,因為 它們有自己的LWP。 把線程綁定到LWP上可以精確地控制調度。???但這種控制在很多非綁定線程競 爭一個LWP是不可能的。 實時線程可以對外部事件有更快的反應。考慮一個用鼠標控制的線程,它必須對 鼠標事件及時作出反應。通過綁定一個線程到LWP上,保証了在需要時會有LWP可用。通 過將LWP設定為實時調度類型,可以保証LWP對LWP事件作出快速響應。 4.5.4信號等待(SIGWAITING)--給等待線程創建LWP 線程庫通常保証在緩沖池內有足夠的LWP保証程序運行。如果進程中所有的LWP處 無限等待的阻塞狀態(例如在讀中斷或網絡時阻塞),操作系統給進程發送一個新的 信號,SIGWAITING。這個信號由線程庫來控制。如果進程中有一個等待運行的線程,一 個新的LWP被創建並被賦予適當的線程使之執行。 SIGWAITING機制在一個或多個線程處計算綁定並且有新線程可以執行的情況下。 一個計算綁定線程可以阻止在缺少LWP的情況下有多個可運行的線程啟動運行。這可以 通過調用thr_setconcurrency(3T)或者在調用thr_create(3T)時使用THR_NEW_LWP標志。 4.5.5確定LWP的已空閑時間 如果活動線程的數量減少,LWP池中的一些LWP將不再被需要。如果LWP的數量比活動 的線程多,線程庫破壞那些沒有用的LWP。線程庫確定LWP的空閑的時間--如果線程在 足夠長的時間內沒有被使用(現在的設置是5分鐘),它們將被刪除。 4.6擴展傳統的信號 為了適應多線程,UNIX的信號模型以一種相當自然的方式被擴展。信號的分布是用 傳統機制建立在進程內部的(signal(2),sigaction(2), 等等)。 如果一個信號控制器被標志為SIG_DFL或者SIG_IGN,在收到信號所採取的行動 (exit, core dump, stop, continue, or ignore)在整個接收進程中有效,將影響到 進程中的所有線程。關信號的基本信息請參見signal(5)。 每個線程有它自己的信號掩模。如果線程使用的內存或其他狀態也在被信號控制 器使用,線程會關一些信號阻塞。進程中所有的線程共享由sigaction(2)和其變量 建立的信號控制器,???象通常那樣。 進程中的一個線程不能給另一個進程中的線程發送信號。一個由 kill(2)和sigsend(2)送出的信號是在進程內部有效的,將被進程中的任何一個接收態 的線程接收並處理。 非綁定線程不能使用交互的信號棧。一個綁定線程可以使用交互信號棧,因為其 狀態是和執行資源連接的。一個交互信號棧必須通過sigaction(2) ,以及 sigaltstack(2)來聲明並使能。 一個應用程序給每個進程一個信號控制器,在它的基礎上,每個線程都有線程信 號控制器。一種辦法是給在一張表中給每個線程控制器建立一個索引,由進程信號控制 器來通過這張表實現線程控制器。這裡沒有零線程。 信號被分為兩類:陷阱(traps)和意外(exceptions,同步信號)和中斷 (interrupts,異步信號)。 在傳統的UNIX中,如果一個信號處掛起狀態(即等待接收),發生的其他同樣的 信號將沒有效果--掛起信號由一位來表示,而不是一個計數器。 就象在單線程的進程裡那樣,如果一個線程在關系統調用阻塞時收到一個信號, 線程將提前返回,或者帶有一個EINTR錯誤代碼,或者帶有比請求少的字節數(如果阻 塞在I/O狀態)。 對多線程編程有特殊意義的是作用在cond_wait(3T)上的信號的效果。這個調用 通常在其他線程調用cond_signal(3T)和cond_broadcast(3T),但是,如果等待線程收 到一個UNIX信號,將返回一個EINTR錯誤代碼。更多的信息參見"對條件變量的等待中斷"。 4.6.1同步信號 陷阱(例如SIGILL, SIGFPE, SIGSEGV)發生在線程自身的操作之,例如除零 錯誤或者顯式地發信號給自身。一個陷阱僅僅被導致它的線程類控制。進程中的幾個 線程可以同時產生和控制同類陷阱。 擴展信號到獨立線程的主張對同步信號來說是容易的--信號被導致問題的線程 來處理。然而,如果一個線程沒有處理這個問題,例如通過sigaction(2)建立一個信號 控制器,整個進程將終止。 因為一個同步信號通常意味著整個進程的嚴重錯誤,而不只是一個線程,終止進程 通常是一個明智的做法。 4.6.2異步信號 中斷(例如SIGINT和SIGIO)是與任何線程異步的,它來自進程外部的一些操作。 它們也許是顯式地送到其他線程的信號,或者是例如Control-c的外部操作,處理異步 信號不處理同步信號要復雜的多。 一個中斷被任何線程來處理,如果線程的信號掩模允許的話。如果有多個線程可以 接收中斷,只有一個被選中。 如果並發的多個同樣的信號被送到一個進程,每一個將被不同的線程處理,如果 線程的信號掩模允許的話。如果所有的線程都屏蔽該信號,則這些信號掛起,直到有信 號解除屏蔽來處理它們。 4.6.3連續語義(Continuation Semantics) 連續語義(Continuation Semantics)是處理信號的傳統方法。其思想是當一個 信號控制器返回,控制恢復到中斷前的狀態。這非常適用單線程進程的異步信號,如 同在示例4-1中的那樣。在某些程序設計語言裡(例如PL/1),這也被用意外 (exception)處理機制。 Code Example 4-1 連續語義 Unsigned int nestcocunt; Unsigned int A(int i, int j) { Nestcount++; If(i==0) Return (j+1); Else if (j==0) Return (A(I-1,1)); Else Return (A(I-1,A(I, j-1))); } void sig(int i){ printf("nestcount=%d\n",nestcount); } main(){ sigset(SIGINT, sig); A(4,4); } 4.6.4對信號的新操作 對多線程編程的幾個新的信號操作被加入操作系統。 Thr_sigsetmask(3T) Thr_sigsetmask(3T)針對線程而sigprocmask(2)針對進程--它設置(線程)的 信號掩模。如果一個新線程被創建,它的初始信號掩模從父線程那裡繼承。 在多線程編程中避免使用sigprocmask(),因為它設置LWP的信號掩模,被這個 操作影響的線程可以在一段時間改變。??? 不象sigprocmask(),thr_sigsetmask()是一種代價相對低廉的調用,因為它不 產生系統調用。 Thr_kill(3T) Thr_kill是kill(2)的線程版本--它發送信號給特定的線程。 當然,這與發送信號給進程不同。如果一個信號被發送給進程,信號可以被進 程中的任何線程所控制。一個由thr_kill()發出的信號只能被指定的線程處理。 注意,你只能用thr_kill()給當前進程裡的線程發信號。這是因為線程標識符 是本地的--不可能給其他進程內的線程命名。 Sigwait(2) Sigwait(2)導致調用線程阻塞直到收到set參數指定的所有信號。線程在等待時, 被set標識的信號應當被解除屏蔽,但最初的信號掩模在調用返回時將恢復。 用sigwait()來從異步信號當中把線程分開。你可以創建一個線程來監聽異步信 號,而其它線程被創建來關指定的異步信號阻塞。 如果信號被發送,sigwait()清除掛起的信號,返回一個數。許多線程可以同時 調用sigwait(),但每個信號被收到只有相關的一個線程返回。 通過sigwait()你可以同時處理異步信號--一個線程通過簡單的sigwait()調用 來處理信號,在信號一旦被受到就返回。如果保証所有的線程(包括調用sigwait() 的線程)屏蔽這樣的信號,你可以保証這樣的信號被你指定的線程安全地處理。 通常,用sigwait()創建一個或多個線程來等待信號。因為sigwait()可以接收 被屏蔽的信號,應當保証其它線程對這樣的信號不感興趣,以免信號被偶然地發送給 這樣的線程。如果信號到達,一個線程從sigwait()返回,處理該信號,等待其它的 信號。處理信號的線程不限使用異步安全函數,可以和其它線程以通常的方式同 步(異步安全函數類型被定義為"安全等級的MT界面MT Interface Safety Levels)。 --------------------------------------- 注意-sigwait()不能用同步信號 --------------------------------------- sigtimedwait(2) sigtimedwait(2)類似sigwait(2),不過如果在指定時間內沒有收到信號, 它出錯並返回。 4.6.5面向線程的信號(thread-directed signals) UNIX信號機制擴展了一個叫做"線程引導信號"的概念。它們就象普通的異步信 號一樣,只不過他們被送到指定線程,而不是進程。 在單獨的線程內等待信號比安裝一個信號控制器安全和容易。 處理異步信號的更好的辦法是同時處理它們。通過調用sigwait(2),一個線程 可以等待一個信號發生。 Code Example 4-2 異步信號和sigwait(2) Main(){ Sigset_t set; Void runA(void); Sigemptyset(&set); Sigaddset(&set, SIGINT); Thr_sigsetmask(SIG_BLOCK, &set, NULL); Thr_create(NULL, 0, runA, NULL, THR_DETACHED, NULL); While(1){ Sigwait(&set); Printf("nestcount=%d\n",nestcount); } } void runA(){ A(4,4); Exit(0); } 這個例子改變了示例4-1:主函數屏蔽了SIGINT信號,創建了一個子線程來調 用前例中的函數A,然用sigwait來處理SIGINT信號。 注意信號在計算線程中被屏蔽,因為計算線程繼承了主線程的信號掩模。除非 用sigwait()阻塞,主線程不會接收SIGINT。 而且,注意在使用sigwait()中,系統調用不會被中斷。 4.6.6完成語義(Completion Semantics) 處理信號的另外一種辦法是用完成語義。完成語義使用在信號表明有極嚴重的 錯誤發生,以至當前的代碼塊沒有理由繼續運行下去。該代碼將被停止執行,取 而代之的是信號控制器。換句話說,信號控制器完成代碼塊。 在示例4-3中,有問題的塊是if語句的then部分。調用setjmp(3C)在jbuf中保 存寄存器當前的狀態並返回零--這樣執行了塊。 Code Example 4-3 完成語義 Sigjmp_buf jbuf; Void mult_divide(void) { Int a,b,c,d; Void problem(); Sigset(SIGFPE, problem); While(1) { If (sigsetjmp(&jbuf) ==0) { Printf("three numbers, please:\n"); Scanf("%d %d %d", &a,&b,&c); D=a*b/c; Printf("%d*%d/%d=%d\n",a,b,c,d); } } } void problem(int sig){ printf("couldn't deal with them,try again\n"); siglongjmp(&jbuf,1); } 如果SIGFPE(一個浮點意外)發生,信號控制器被喚醒。 信號控制器調用siglongjmp(3C),這個函數保存寄存器狀態到jbuf,導致程序 從sigsetjmp()再次返回(保存的寄存器包含程序計數器和堆棧指針)。 然而,這一次,sigsetjmp(3C)返回siglongjmp()的第二個參數,是1。注意塊 被跳過,在while循環的下一次重復才會執行。 注意,你可以在多線程編程中用sigsetjmp(3C)和siglongjmp(3C),但是要小心, 線程永遠不會用另一個線程的sigsetjmp()的結果來做siglongjmp()。而且, sigsetjmp()和siglongjmp()保存和恢復信號掩模,但sigjmp(3C)和longjmp(3C) 不會這樣做。如果你使用信號控制器時最好使用sigsetjmp()和siglongjmp()。 完成語義經常用來處理意外。具體的,Ada語言使用這種模型。 -------------------------------------- 注意-sigwait(2)永遠不應用來同步信號。 -------------------------------------- 4.6.7信號控制器和異步安全 有一個類似與線程安全的概念:異步安全。異步安全操作被保証不會和被中斷 的操作相混。 如果信號控制器與正被中斷的操作沖突,就會有異步安全的問題。例如,假設 有一個程序正在printf調用的當中,一個信號發生,它的控制器也要調用printf(): 兩個printf()的輸出會交織在一起。為了避免這種結果,如果是printf被中斷,控 制器就不應當調用printf。 這個問題使用同步原語無法解決,因為試圖的同步操作會立即導致死鎖。 例如,假設printf()用互斥鎖來保護它自己。現在假設一個線程正在調用 printf(),第一個printf就得在互斥鎖上等待,但是線程突然被信號中斷了。如果 控制器(被在printf的裡面中斷的線程調用)也調用printf(),在互斥鎖上阻塞的 線程回再次嘗試得到printf的使用權,這就導致了死鎖。 為了避免控制器和操作之間的幹涉,或者保証這種情況永遠不會發生(例如在 可能出問題的時刻封掉所有信號),或者在信號控制器中僅僅使用異步安全操作。 因為在用戶級操作設置線程的掩模相對代價較小,你可以方便地設計代碼使得 它符合異步安全的范疇。 4.6.8關條件變量的中斷等待 如果在線程等待條件變量的時候獲得一個信號,過去的做法是(假設進程沒有 終止)被中斷的調用返回EINTR。 理想的新條件是當cond_wait(3T)和cond_timedwait(3T)返回,將重新獲得互斥 鎖。 Solaris多線程是這樣做的:如果一個線程在cond_wait或cond_timedwait()函 數上阻塞,而且獲得一個沒有被屏蔽信號,(信號)控制器將被啟用,cond_wait() 或cond_timedwait()返回EINTR,並且互斥鎖加鎖。??? 這意味著互斥鎖將被信號控制器獲得,因為控制器必須清理環境。 請看示例4-4 Code Example 4-4 條件變量和等待中斷 Int sig_catcher() { Sigset_t set; Void hdlr(); Mutex_lock(&mut); Sigemptyset(&set); Sigaddset(&set,SIGING); Thr_sigsetmask(SIG_UNBLOCK,&set,0); If(cond_wait(&cond,&mut) == EINTR){ /* signal occurred and lock is held */ cleanup(); mutex_unlock(&mut); return(0); } normal_processing(); mutex_unlock(&mut); return(1); } void hdlr() { /* lock is held in the handler */ ……… } 假設SIGINT信號在sig_catcher()的入口處被阻塞,而且hdlr()已被建立(通 過sigaction()調用)成為SIGINT的控制器。 如果線程阻塞在cond_wait()的時候,一個沒有被屏蔽的信號被送給線程,線 程首先獲得互斥鎖,然調用hdlr(),然從cond_wait()返回EINTR。 注意,在sigaction()中指定SA_RESTART標志是沒有效果的--cond_wait(3T) 不是系統調用,不會被自動重新啟動。如果線程在cond_wait()阻塞時,調用總是 返回EINTR。 4.7 I/O事項 多線程的一個優勢是它的I/O性能。傳統的UNIX API在這一領域沒有給程序員 足夠的輔助--你或者使用文件系統的輔助,或者跳過整個文件系統。 這部分將介紹怎樣在多線程利用I/O並發和多緩沖區來獲得更多的靈活性。這 個部分也探討了同步I/O(多線程)和異步I/O(可以是也可以不是多線程)的異同。 4.7.1 I/O作為遠程過程調用 在傳統的UNIX模型裡,I/O表現為同步的,就象你在通過一個遠程過程調用 (RPC)來操縱外設。一旦調用返回,I/O完成(或至少看上去已完成--例如一個寫 請求,也許僅僅是在操作系統內做數據移動)。 這個模型的優勢在容易理解,因為程序員對過程調用是很熟悉的。 一個代替的辦法(在傳統的UNIX裡沒有的)是異步模式,I/O請求僅僅啟動一 個操作。程序要自己來發現操作是否完成。 這個辦法不象同步方法那樣簡單,但它的優勢在允許並發的I/O處理和傳統 的單線程進程處理。 4.7.2馴服的異步(Tamed Asynchrony) 你可以通過在多線程編程裡使用同步I/O來獲得異步I/O的大多數好處。在異 步I/O中,你發出一個請求,過一會兒再去檢查請求是否已經完成,你可以用分離 的線程來同步操作I/O。然由主線程(也許是thr_join(3T))檢查操作是否完成。 4.7.3異步I/O 在大多數情況下沒有必要使用異步I/O,因為它的效果可以通過線程來實現, 每個線程使用同步I/O。然而,在少數情況下,線程不能完全實現實現異步I/O的功 能。 最直接的例子是用流的方法寫磁帶。這種技術在有持續的數據流寫向磁帶,磁 帶驅動器高速運轉時防止磁帶驅動器停止。 為了作到這點,在磁帶驅動程序響應一個標志上一個寫操作已經完成的中斷時, 內核裡的磁帶驅動器必須發出一個寫請求隊列。 線程不能保証異步寫被排隊,因為線程本身執行的順序就是不確定的。例如試 圖給磁帶的寫操作排隊是不可能的。 *異步I/O操作 #include int aioread(int filedes, char *bufp, int bufs, off_t offset, int whence, aio_result_t *resultp); int aiowrite(int filedes, const char *bufp, int bufs, off_t offset, int whence, aio_result_t *resultp); aio_result_t *aiowait(const struct timeval *timeout); int aiocancel(aio_result_t *resultp); aioread(3)和aiowrite(3)在形式上與pread(2)和pwrite(2),不同的是最一 個參數。調用aioread()和aiowrite()導致初始化(或排隊)一個I/O操作。 調用不會阻塞,調用的狀態將返回到由resultp指向的結構。其類型為 aio_result_t,包含有: int aio_return; int aio_errno; 如果一個調用立即失敗,錯誤碼被返回到aio_errno。否則,這個域包含 AIO_INPROGRESS,意味著操作被成功排隊。 你可以通過調用aiowait(3)來等待一個特定的異步I/O操作結束。它返回一個 指向aio_result_t數據結構的指針,該結構由最初的aioread(3)或者aiowrite(3) 提供。如果這些函數被調用,Aio_result包含類似與read(2)和write(2)相似返回 值,aio_errno包含錯誤代碼,如果有的話。 Aiowait()使用一個timeout參數,該參數指定了調用者可以等多久。通常情況 下,一個NULL指針表示調用者希望等待的時間不確定,如果指針指向的數據結構包 含零值,表明調用者不希望等待。 你可以啟動一個異步I/O操作,做一些工作,然調用aiowait()來等待結束的 請求。或者你可以在操作結束,用SIGIO來異步地通知。 最,一個掛起的異步I/O操作可以通過調用aiocancel()來取消。這個過程在 調用時使用存放結果的地址做參數。這個結果區域標識了要取消哪一個操作。 4.7.4共享的I/O和新的I/O系統調用 如果多個線程同時使用同一個文件描述符來進行I/O操作,你會發現傳統的 UNIX I/O接口不安全。在非串行的I/O(即並發)發生時會有問題。它使用 lseek(2)系統調用來為續的read(2)和write(2)函數設置文件偏移量。如果兩個或 更多的線程使用lseek(2)來移動同一個文件描述符,就會發生沖突。 為了避免沖突,使用新的pread(2)和pwrite(2)系統調用。 #include #include ssize_t pread(int fildes,void *buf,size_t nbyte,off_t offset); ssize_t pwrite(int filedes,void *buf,size_t nbyte,off_t offset); 這些調用效果類似read(2)和write(2),不同之處在多了一個參數,文件 偏移量。用這個參數,你可以用不著用lseek(2)指定偏移量,多線程可以對同一個 文件描述符進行安全的操作。 4.7.5 Getc(3S)和putc(3S)的替代函數 一個問題會發生在標準I/O的情況下。程序員可以很快地習慣getc(3S)和 putc(3S)這樣的函數--它們是用宏來實現的。因為如此,他們可以在程序的循環內 部使用,用不著考慮效率。 然而,如果改用線程安全的版本,代價會突然變的昂貴--它們需要(至少) 兩個內部子程序調用,來給一個互斥鎖加鎖和解鎖。為了解決這個問題,提供了這 些函數的替代版本--getc_unlocked(3S)和putc_unlocked(3S)。 這些函數不給互斥鎖加鎖,因此速度象非線程安全版本的getc(3S)和putc(3S) 一樣快。然而如果按照線程安全的方法來使用的話,必須用flockfile(3S)和 funlockfile(3S)顯式地給互斥鎖加鎖和解鎖來保護標準的I/O流。這兩個調用放在 循環外面,而getc_unlocked()或者putc_unlocked()放在循環內部。
(http://www.fanqiang.com)
進入【UNIX論壇】
|
|
| 相關文章 |
Solaris2.4 多線程編程指南7--編程指南 (2002-01-29 20:33:46) Solaris2.4 多線程編程指南6--編譯和調試 (2002-01-29 20:32:52) Solaris2.4 多線程編程指南5--安全和不安全的接口函數 (2002-01-29 20:32:05) Solaris2.4 多線程編程指南4--操作系統編程 (2002-01-29 20:29:36) Solaris2.4 多線程編程指南3--使用同步對象編程 (2002-01-29 20:28:07) Solaris2.4 多線程編程指南2--用多線程編程 (2002-01-29 20:26:32) Solaris2.4 多線程編程指南1--線程基礎 (2002-01-29 20:25:25) Linux下的多線程編程 (2001-08-11 09:05:00)
|
|
|
|
 |
★ 樊強制作 歡迎分享 ★ |