[ 永遠的UNIX::UNIX技術資料的寶庫 ]   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)
 

★  樊強制作 歡迎分享  ★