久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

linux AIO (異步IO) 那點事兒

 Liucw2012 2013-01-28




        在高性能的服務(wù)器編程中,,IO 模型理所當(dāng)然的是重中之重,,需要謹慎選型的,對于網(wǎng)絡(luò)套接字,,我們可以采用epoll 的方式來輪詢,,盡管epoll也有一些缺陷,但總體來說還是很高效的,,尤其來大量套接字的場景下,;但對于Regular File 來說,是不能夠用采用 poll/epoll 的,,即O_NOBLOCK 方式對于傳統(tǒng)文件句柄是無效的,,也就是說我們的 open ,read, mkdir 之類的Regular File操作必定會導(dǎo)致阻塞.在多線程、多進程模型中,,可以選擇以同步阻塞的方式來進行IO操作,,任務(wù)調(diào)度由操作系統(tǒng)來保證公平性,但在單進程/線程模型中,,以nodejs 為例 ,,假如 我們需要在一個用戶請求中處理10個文件: 

function fun() {

fs.readFileSync();
fs.readFileSync();

}

這時候進程至少會阻塞10次,而這可能會導(dǎo)致其他的上千個用戶請求得不到處理,,這當(dāng)然是不能接受的. 

Linux AIO 早就被提上議程,,目前比較知名的有 Glibc 的 AIO   與 Kernel Native AIO 
Glibc AIO: http://www.ibm.com/developerworks/linux/library/l-async/ 
Kernel Native AIO: http://lse./io/aio.html 

我們用Glibc 的AIO 做個小實驗,,寫一個簡單的程序:異步方式讀取一個文件,并注冊異步回調(diào)函數(shù): 
int  main()

{
struct aiocb my_aiocb;
fd = open("file.txt", O_RDONLY);
...
my_aiocb.aio_sigevent.sigev_notify_function = aio_completion_handler;

ret = aio_read(&my_aiocb);

write(1, "caller thread\n", 14);
sleep(5);
}

void aio_completion_handler(sigval_t sigval)
{
write(1, "callback\n", 9);
struct aiocb *req;
...
req = (struct aiocb *)sigval.sival_ptr;
printf("data: %s\n" ,req->aio_buf);
return;
}

我們用 strace 來跟蹤調(diào)用,,得到以下結(jié)果 (只保留主要語句): 

23908 open("file.txt", O_RDONLY)        = 3 
23908 clone(...) = 23909 
23908 write(1, "caller thread\n", 14)   = 14 
23908 nanosleep({5, 0},   
... 
23909 pread(3, "hello, world\n", 1024, 0) = 13 
23909 clone(..)= 23910 
23909 futex(0x3d3a4082a4, FUTEX_WAIT_PRIVATE, 1, {0, 999942000} 
... 
23910 write(1, "callback\n", 9)         = 9 
23910 write(1, "data: hello, world\n", 19) = 19 
23910 write(1, "\n", 1)                 = 1 
23910 _exit(0)                          = ? 
23909 <... futex resumed> )             = -1 ETIMEDOUT (Connection timed out) 
23909 futex(0x3d3a408200, FUTEX_WAKE_PRIVATE, 1) = 0 
23909 _exit(0)                          = ? 
23908 <... nanosleep resumed> {5, 0})   = 0 
23908 exit_group(0)                     = ? 

在Glibc AIO 的實現(xiàn)中,, 用多線程同步來模擬 異步IO ,以上述代碼為例,,它牽涉了3個線程,, 
主線程(23908)新建 一個線程(23909)來調(diào)用 阻塞的pread函數(shù),當(dāng)pread返回時,,又創(chuàng)建了一個線程(23910)來執(zhí)行我們預(yù)設(shè)的異步回調(diào)函數(shù),, 23909 等待23910結(jié)束返回,然后23909也結(jié)束執(zhí)行.. 

實際上,,為了避免線程的頻繁創(chuàng)建,、銷毀,當(dāng)有多個請求時,,Glibc AIO 會使用線程池,,但以上原理是不會變的,尤其要注意的是:我們的回調(diào)函數(shù)是在一個單獨線程中執(zhí)行的. 
Glibc AIO 廣受非議,,存在一些難以忍受的缺陷和bug,,飽受詬病,是極不推薦使用的. 
詳見:http:///davpage/linux/async-io.html 

在Linux 2.6.22+ 系統(tǒng)上,,還有一種 Kernel AIO 的實現(xiàn),,與Glibc 的多線程模擬不同 ,它是真正的做到內(nèi)核的異步通知,,比如在較新版本的Nginx 服務(wù)器上,,已經(jīng)添加了AIO方式 的支持. 

http://wiki./HttpCoreModule 
aio 
syntax: aio [on|off|sendfile] 
default: off 
context: http, server, location 
This directive is usable as of Linux kernel 2.6.22. For Linux it is required to use directio, this automatically disables sendfile support

location /video { 
aio on; 
directio 512; 
output_buffers 1 128k; 


聽起來Kernel Native AIO 幾乎提供了近乎完美的異步方式,但如果你對它抱有太高期望的話,,你會再一次感到失望. 

目前的Kernel AIO 僅支持 O_DIRECT 方式來對磁盤讀寫,,這意味著,你無法利用系統(tǒng)的緩存,,同時它要求讀寫的的大小和偏移要以區(qū)塊的方式對齊,參考nginx 的作者 Igor Sysoev 的評論: http://forum./read.php?2,113524,113587#msg-113587 

nginx supports file AIO only in 0.8.11+, but the file AIO is functional 
on FreeBSD only. On Linux AIO is supported by nginx only on kerenl 
2.6.22+ (although, CentOS 5.5 has backported the required AIO features). 
Anyway, on Linux AIO works only if file offset and size are aligned 
to a disk block size (usually 512 bytes) and this data can not be cached 
in OS VM cache (Linux AIO requires DIRECTIO that bypass OS VM cache). 
I believe a cause of so strange AIO implementaion is that AIO in Linux 
was developed mainly for databases by Oracle and IBM. 

同時注意上面的橙色字部分,,啟用AIO 就會關(guān)閉sendfile -這是顯而易見的,當(dāng)你用Nginx作為靜態(tài)服務(wù)器,,你要么選擇以AIO 讀取文件到用戶緩沖區(qū),然后發(fā)送到套接口,,要么直接調(diào)用sendfile發(fā)送到套接口,sendfile 雖然會導(dǎo)致短暫的阻塞,,但開啟AIO 卻無法充分的利用緩存,也喪失了零拷貝的特征 ;當(dāng)你用Nginx作為動態(tài)服務(wù)器,,比如 fastcgi + php 時,,這時php腳本中文件的讀寫是由php 的 文件接口來操作的,這時候是多進程+同步阻塞模型,和文件異步模式扯不上關(guān)系的. 

所以現(xiàn)在Linux 上,,沒有比較完美的異步文件IO 方案,,這時候苦逼程序員的價值就充分體現(xiàn)出來了,libev 的作者 Marc Alexander Lehmann 老大就重新實現(xiàn)了一個AIO library : 

http://software./pkg/libeio.html 

其實它還是采用線程池+同步模擬出來的,,和Glibc 的 AIO 比較像,,用作者的話說,這個庫相比與Glibc 的實現(xiàn),,開銷更小,,bug更少(不然重新造個輪子還有毛意義呢?反正我是信了) ,,不過這個輪子的代碼可讀性實在不敢恭維,,Marc 老大自己也說了:Currently in BETA! Its code, documentation, integration and portability quality is currently below that of libev, but should soon be ready for use in production environments. 

(其實libev代碼和文檔可讀性也不咋地,貌似驅(qū)動內(nèi)核搞多了都這樣,?)好吧,,腹誹完了,我們還是閱讀下它的源碼 ,,來稍微分析一下它的原理: 

(這個文章的流程圖還是蠻靠譜的:http:///blog/?p=244  ,,此處更詳細的補充一下下) 

int eio_init (void (*want_poll)(void), void (*done_poll)(void)) 

初始化時設(shè)定兩個回調(diào)函數(shù),它有兩個全局的數(shù)據(jù)結(jié)構(gòu) : req 存放請求隊列,,res 存放已經(jīng)完成的隊列 當(dāng)我,,當(dāng)你提交一個異步請求時(eio_submit),其實是放入req隊列中,,然后向線程池中處于信號等待的線程發(fā)送信號量(如果線程池中沒有線程就創(chuàng)建一個),,獲得信號的線程會執(zhí)行如下代碼: 
ETP_EXECUTE (self, req);

X_LOCK (reslock);
++npending;
if (!reqq_push (&res_queue, req) && want_poll_cb)
want_poll_cb ();
X_UNLOCK (reslock);

ETP_EXECUTE 就是實際的阻塞調(diào)用,比如read,,open,,,sendfile之類的,,當(dāng)函數(shù)返回時,表明操作完成,,此時加鎖方式向完成隊列添加一項 ,然后調(diào)用 want_pool ,這個函數(shù)是我們eio_init時候設(shè)置的,,然后釋放鎖。 

注意:每次完成任務(wù)時,,都要調(diào)用want_poll ,,所以這個函數(shù)應(yīng)該是線程安全且盡量短促,實際上我們?yōu)榱吮苊庀萑攵嗑€程的泥淖,,我們往往配合eio使用事件輪詢機制,,比如:我們創(chuàng)建一對管道,,我們把“讀”端的管道加入 epoll 監(jiān)控結(jié)構(gòu)中,want_poll 函向“寫”端管道寫數(shù)入一個字節(jié)或字長 ,,所以當(dāng)下次epoll_wait 返回時,,我們會執(zhí)行 “讀” 端管道的 回調(diào)函數(shù),類似如下: 
void r_pipe_cb(){

...
eio_poll();
}

在eio_poll 中 有類似以下代碼: 
for(;;){

X_LOCK (reslock);
req = reqq_shift (&res_queue);
if (req){
if (!res_queue.size && done_poll_cb)
done_poll_cb ();
}
X_UNLOCK (reslock);
res = ETP_FINISH (req);
...
if(empty) break;

}

eio_poll 函數(shù)就是從完成隊列res 依次shift ,,依次執(zhí)行我們的回調(diào)函數(shù)(ETP_FINISH 就是執(zhí)行用戶回調(diào)),,在取出完成隊列的最后一項但還沒有執(zhí)行用戶回調(diào)之前,調(diào)用我們設(shè)定的done_poll ,對res隊列的操作當(dāng)然也是加鎖的,,注意此時我們自定義的異步回調(diào)函數(shù)是在我們的主線程中執(zhí)行的,!這才是我們的最終目的! 

在eio 線程池中,,默認最多4個線程,,在高性能的程序中,過多的進程/線程往往也是一個瓶頸,, 
寄存器的進出棧還是其次,,進程虛存地址切換、各級cache 的miss ,,這才是最昂貴的,,所以,最理想的情形就是:有幾個cpu ,,就有同樣數(shù)目的active  線程/進程,,但因為io線程往往會陷入sleep模式,所以,,還是需要額外的待切換的線程的,,作為經(jīng)驗法則,線程池的數(shù)量最好是cpu 的數(shù)目 X 2(參見windows 核心編程 IOCP卷). 

libeio 雖不完美,,但目前還是將就著用用吧 ... 

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點,。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,謹防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多