1. 程式人生 > >linux AIO (非同步IO) 那點事兒

linux AIO (非同步IO) 那點事兒

在高效能的伺服器程式設計中,IO 模型理所當然的是重中之重,需要謹慎選型。對於網路套接字,我們可以採用epoll 的方式來輪詢,儘管epoll也有一些缺陷,但總體來說還是很高效的,尤其來大量套接字的場景下;但對於Regular File 來說,是不能夠用採用 poll/epoll 的,即O_NOBLOCK 方式對於傳統檔案控制代碼是無效的,也就是說我們的 open ,read, mkdir 之類的Regular File操作必定會導致阻塞。在多執行緒、多程序模型中,可以選擇以同步阻塞的方式來進行IO操作,任務排程由作業系統來保證公平性,但在單程序/執行緒模型中,以nodejs 為例 ,假如 我們需要在一個使用者請求中處理10個檔案: 



function fun() {

 fs.readFileSync();

 fs.readFileSync();

 …

 }

這時候程序至少會阻塞10次,而這可能會導致其他的上千個使用者請求得不到處理,這當然是不能接受的. 

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

我們用Glibc 的AIO 做個小實驗,寫一個簡單的程式:非同步方式讀取一個檔案,並註冊非同步回撥函式:

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 來跟蹤呼叫,得到以下結果 (只保留主要語句): 

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 的實現中, 用多執行緒同步來模擬 非同步IO ,以上述程式碼為例,它牽涉了3個執行緒, 
主執行緒(23908)新建 一個執行緒(23909)來呼叫 阻塞的pread函式,當pread返回時,又建立了一個執行緒(23910)來執行我們預設的非同步回撥函式, 23909 等待23910結束返回,然後23909也結束執行.. 

實際上,為了避免執行緒的頻繁建立、銷燬,當有多個請求時,Glibc AIO 會使用執行緒池,但以上原理是不會變的,尤其要注意的是:我們的回撥函式是在一個單獨執行緒中執行的. 
Glibc AIO 廣受非議,存在一些難以忍受的缺陷和bug,飽受詬病,是極不推薦使用的. 
詳見:http://davmac.org/davpage/linux/async-io.html 

在Linux 2.6.22+ 系統上,還有一種 Kernel AIO 的實現,與Glibc 的多執行緒模擬不同 ,它是真正的做到核心的非同步通知,比如在較新版本的Nginx 伺服器上,已經添加了AIO方式 的支援. 

http://wiki.nginx.org/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 方式來對磁碟讀寫,這意味著,你無法利用系統的快取,同時它要求讀寫的的大小和偏移要以區塊的方式對齊,參考nginx 的作者 Igor Sysoev 的評論: http://forum.nginx.org/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 就會關閉sendfile -這是顯而易見的,當你用Nginx作為靜態伺服器,你要麼選擇以AIO 讀取檔案到使用者緩衝區,然後傳送到套介面,要麼直接呼叫sendfile傳送到套介面,sendfile 雖然會導致短暫的阻塞,但開啟AIO 卻無法充分的利用快取,也喪失了零拷貝的特徵 ;當你用Nginx作為動態伺服器,比如 fastcgi + php 時,這時php指令碼中檔案的讀寫是由php 的 檔案介面來操作的,這時候是多程序+同步阻塞模型,和檔案非同步模式扯不上關係的. 

所以現在Linux 上,沒有比較完美的非同步檔案IO 方案,這時候苦逼程式設計師的價值就充分體現出來了,libev 的作者 Marc Alexander Lehmann 老大就重新實現了一個AIO library : 

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

其實它還是採用執行緒池+同步模擬出來的,和Glibc 的 AIO 比較像,用作者的話說,這個庫相比與Glibc 的實現,開銷更小,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程式碼和文件可讀性也不咋地,貌似驅動核心搞多了都這樣?)好吧,腹誹完了,我們還是閱讀下它的原始碼 ,來稍微分析一下它的原理: 

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

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

初始化時設定兩個回撥函式,它有兩個全域性的資料結構 : req 存放請求佇列,res 存放已經完成的佇列 當我,當你提交一個非同步請求時(eio_submit),其實是放入req佇列中,然後向執行緒池中處於訊號等待的執行緒傳送訊號量(如果執行緒池中沒有執行緒就建立一個),獲得訊號的執行緒會執行如下程式碼: 
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 就是實際的阻塞呼叫,比如read,open,,sendfile之類的,當函式返回時,表明操作完成,此時加鎖方式向完成佇列新增一項 ,然後呼叫 want_pool ,這個函式是我們eio_init時候設定的,然後釋放鎖。 

注意:每次完成任務時,都要呼叫want_poll ,所以這個函式應該是執行緒安全且儘量短促,實際上我們為了避免陷入多執行緒的泥淖,我們往往配合eio使用事件輪詢機制,比如:我們建立一對管道,我們把“讀”端的管道加入 epoll 監控結構中,want_poll 函向“寫”端管道寫數入一個位元組或字長 ,所以當下次epoll_wait 返回時,我們會執行 “讀” 端管道的 回撥函式,類似如下: 
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 函式就是從完成佇列res 依次shift ,依次執行我們的回撥函式(ETP_FINISH 就是執行使用者回撥),在取出完成佇列的最後一項但還沒有執行使用者回撥之前,呼叫我們設定的done_poll ,對res佇列的操作當然也是加鎖的,注意此時我們自定義的非同步回撥函式是在我們的主執行緒中執行的!這才是我們的最終目的! 

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

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

相關推薦

linux AIO 非同步IO 事兒

在高效能的伺服器程式設計中,IO 模型理所當然的是重中之重,需要謹慎選型。對於網路套接字,我們可以採用epoll 的方式來輪詢,儘管epoll也有一些缺陷,但總體來說還是很高效的,尤其來大量套接字的場景下;但對於Regular File 來說,是不能夠用採用 poll/e

Selector非同步IO

非同步IO是一種沒有阻塞的讀寫資料的方法,通常,在程式碼進行 read() 呼叫時,程式碼會阻塞直至有可供讀取的資料。同樣,write() 呼叫將會阻塞直至資料能夠寫入。 非同步 I/O 的一個優勢在於,它允許您同時根據大量的輸入和輸出執行 I/O。同步程式常常要求助於輪

Linux IO模式BIO、NIO、IO多路複用、非同步IO及 select、poll、epoll詳解

同步IO和非同步IO,阻塞IO和非阻塞IO分別是什麼,到底有什麼區別?不同的人在不同的上下文下給出的答案是不同的。所以先限定一下本文的上下文。 本文討論的背景是Linux環境下的network IO。 一 概念說明 在進行解釋之前,首先要說明幾個概念: -

Linux動態連結事兒`cmake find_package,linux shared library`路徑詳解

Motivation 經常在Linux下面寫C++程式,尤其是需要整合各種第三方庫的工程,肯定對find_package指令不陌生。 這是條很強大的指令。可以直接幫我們解決整個工程的依賴問題,自動把標頭檔案和動態連結檔案配置好。比如說,在Linux下面工程依賴了OpenCV,只需

Netty之BIO同步阻塞IO、PIO非同步阻塞IO、NIO非同步非阻塞IOAIO非同步非阻塞IO

學習書籍:Netty權威指南 多種IO方式的比較: 1、BIO(同步阻塞IO) 使用ServerSocket繫結IP地址和監聽埠,客戶端發起連線,通過三次握手建立連線,用socket來進行通訊,通過輸入輸出流的方式來進行同步阻塞的通訊 每次客戶端發起連線請求,都會

Netty之BIO同步阻塞IO、PIO非同步阻塞IO、NIO非同步非阻塞IOAIO非同步非阻塞IO、Netty

學習書籍:Netty權威指南 多種IO方式的比較: 1、BIO(同步阻塞IO) 使用ServerSocket繫結IP地址和監聽埠,客戶端發起連線,通過三次握手建立連線,用socket來進行通訊,通過輸入輸出流的方式來進行同步阻塞的通訊 每次客戶端發起連線請求,都會啟動一個執

Linux下五種I/O模型詳解阻塞IO、非阻塞IOIO複用、訊號驅動、非同步IO

文章轉載自微信公眾號:漫話程式設計 1 什麼是I/O 程式是由資料+指令構成的,執行程式的過程可以分成下面這幾步: 1.將程式碼載入到記憶體中,逐條執行記憶體中的程式碼 2.在執行程式碼的過程中,可能需要對檔案的讀寫,即將檔案輸入(Input)

談談源碼管理事兒——源碼管理十誡

我不 evel .html 文件夾 jetbrains enable thum XML 構建 引言: 若是還有能夠毫無偏見地涉及各個編程語言。比源碼管理軟件更必要的工具。我倒是非常想見識一下。源碼管理軟件是我們工作的必備工具,是很多開發團隊的血液。那為什麽我們都

Maven 事兒

做到 conn active cep ant tab name www color 0. 前言 Jason Van Zyl,在 Java 十大風雲人物排行榜上或許會看到他。 這兄弟是幹嘛的? 他就是 Maven 的創始人,人們都尊稱他為“Maven 他爸&

裝置IO之一mmap、直接IO以及非同步IO

現在,在linux中經常可以看到在使用者空間編寫的驅動程式,比如X伺服器,一些廠商的私有驅動等等,這就意味著使用者空間取得了對硬體的訪問能力,這通常是通過mmap將裝置記憶體對映到了使用者程序空間,從而使得使用者可以通過讀寫這些記憶體來獲取對硬體的訪問能力。 核心一般會對I/O操作進行緩衝以獲取

資料分析事兒

在之前我們給大家講了講什麼是資料分析以及資料分析的目的,資料分析就是通過使用合適的方法進行統計,統計也不是隨隨便便的統計的,需要找對方法。統計分析方法對收集來的大量資料進行分析,提取有用資訊和形成結論而對資料加以詳細研究和概括總結的過程。而資料分析的目的就是通過分析資料找到企業未來的發展情況。今天就給大家

Maven事兒Eclipse版

前言:   由於最近工作學習,總是能碰到Maven的原始碼。雖然平時工作並不使用Maven,但是為了學習一些原始碼,還是必須要了解下。這篇文章不是一個全面的Maven解析,而是一個簡單的介紹,包括Eclipse中如何使用Maven,如何利用Maven工程學習。   循序漸進

Android app 線上更新事兒適配Android6.0、7.0、8.0

一、前言 app線上更新是一個比較常見需求,新版本釋出時,使用者進入我們的app,就會彈出更新提示框,第一時間更新新版本app。線上更新分為以下幾個步驟: 1, 通過介面獲取線上版本號,versionCode 2, 比較線上的versionCode 和本地的versi

《小老爺們事兒》終極整理貼 已完結

以下是分帖連結地址 小老爺們那點事兒整理帖之一 http://www.tulaoya.cn/bbs/read.php?tid=4 小老爺們那點事兒整理帖之二    避孕套的故事 http://www.tulaoya.cn/bbs/read.php?tid=5 小老爺們那點事兒整理帖之三  我發育了 http:

資料分析事兒

隨著資料分析越來越火熱,資料分析師逐漸成為炙手可熱的職業,正是由於這些,使得很多人都想進入資料分析這個行業。在進入資料分析這個行業之前我們需要對資料分析進行了解,如果不瞭解的話,那麼能否成為一名合格的資料分析師會是人們質疑的物件。現在就給大家講講資料分析那點事兒。 首先給大家說說什

多執行緒的事兒1--如何選擇執行緒數

  多執行緒向來是一個讓程式設計師頭痛的一個問題,不只是初學者容易犯錯誤,很多老鳥也難免站著中槍。一旦出現問題很難定位和解決,除了可能因為程式設計者知識上的缺陷導致的疏漏外,另一個難題就是問題重現難度大,避免多執行緒導致BUG最好的方法就是預防。   首先,在開始進行多執行

Cookie、Session、Token事兒原創

本文已獨家授權 鴻洋( hongyangAndroid ) 公眾號釋出! 前言:新公司專案中使用到了Cookie,在各大Android技術討論群向前輩們取經討論這cookie、session、token這仨哥們的時候,很多開發者說法不一各抒已見,所以是時候回顧下http基礎以及總結開發

產品經理必懂的技術事兒

一:產品思維與技術思維:1:工程師是路徑推理的技術思維,產品經理是使用者場景的產品思維,技術思維表現為——實現方式、技術架構、技術價值、開發成本;而產品思維表現在——使用者價值、使用場景、商業價值、業務閉環。2:產品職能:屬於資訊上游負責發現並定義需求,將使用者需求通過具體的

產品經理必懂的技術事兒

6:非關係型資料庫:非關係型資料庫是一種相對鬆散且可以不按照嚴格的結構規範進行儲存的資料庫。非關係型資料庫一般叫做 NoSQL(Not Only SQL),非關係型資料庫沒有關係型資料庫那樣嚴格的資料結構約束,在儲存的形式和使用上有別於關係型資料庫。現在主流的非關係型資料庫有 MongoDB 和 CouthD

MVC之前的事兒系列8:UrlRouting的理解

文章內容 根據對Http Runtime和Http Pipeline的分析,我們知道一個ASP.NET應用程式可以有多個HttpModuel,但是隻能有一個HttpHandler,並且通過這個HttpHandler的BeginProcessRequest(或ProcessRequest)來處理並返回請求,前