阻塞非阻塞和同步異步

本文轉(zhuǎn)自該處瘾带,由于這篇文章寫的非常好就沒有再單獨(dú)總結(jié)旭旭。感謝作者V惺!;闼摹!
作者:涼拌姨媽好吃
鏈接:http://www.reibang.com/p/6a6845464770
來源:簡書
簡書著作權(quán)歸作者所有液兽,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處换况。


首先引用levin的回答讓我們理清楚五種IO模型

1.阻塞I/O模型(同步阻塞)
老李去火車站買票琅摩,排隊三天買到一張退票此迅。
耗費(fèi):在車站吃喝拉撒睡 3天汽畴,其他事一件沒干旧巾。

2.非阻塞I/O模型(同步非阻塞)
老李去火車站買票耸序,隔12小時去火車站問有沒有退票,三天后買到一張票鲁猩。耗費(fèi):往返車站6次坎怪,路上6小時,其他時間做了好多事廓握。

3.I/O復(fù)用模型
1.select/poll(同步非阻塞)
老李去火車站買票搅窿,委托黃牛,然后每隔6小時電話黃牛詢問隙券,黃牛三天內(nèi)買到票男应,然后老李去火車站交錢領(lǐng)票。
耗費(fèi):往返車站2次娱仔,路上2小時沐飘,黃牛手續(xù)費(fèi)100元,打電話17次
2.epoll(異步非阻塞)
老李去火車站買票牲迫,委托黃牛耐朴,黃牛買到后即通知老李去領(lǐng),然后老李去火車站交錢領(lǐng)票盹憎。
耗費(fèi):往返車站2次筛峭,路上2小時,黃牛手續(xù)費(fèi)100元陪每,無需打電話

4.信號驅(qū)動I/O模型(異步非阻塞)
老李去火車站買票影晓,給售票員留下電話镰吵,有票后,售票員電話通知老李俯艰,然后老李去火車站交錢領(lǐng)票捡遍。
耗費(fèi):往返車站2次,路上2小時竹握,免黃牛費(fèi)100元画株,無需打電話

5.異步I/O模型(異步非阻塞)
老李去火車站買票,給售票員留下電話啦辐,有票后谓传,售票員電話通知老李并快遞送票上門。
耗費(fèi):往返車站1次芹关,路上1小時续挟,免黃牛費(fèi)100元,無需打電話

1.I/O多路復(fù)用

1.1 它的形成原因

如果一個I/O流進(jìn)來侥衬,我們就開啟一個進(jìn)程處理這個I/O流诗祸。那么假設(shè)現(xiàn)在有一百萬個I/O流進(jìn)來,那我們就需要開啟一百萬個進(jìn)程一一對應(yīng)處理這些I/O流(——這就是傳統(tǒng)意義下的多進(jìn)程并發(fā)處理)轴总。思考一下直颅,一百萬個進(jìn)程,你的CPU占有率會多高怀樟,這個實(shí)現(xiàn)方式及其的不合理功偿。所以人們提出了I/O多路復(fù)用這個模型,一個線程往堡,通過記錄I/O流的狀態(tài)來同時管理多個I/O械荷,可以提高服務(wù)器的吞吐能力。

1.2 通過它的英文單詞來理解一下I/O多路復(fù)用

I/O multiplexing 也就是我們所說的I/O多路復(fù)用虑灰,但是這個翻譯真的很不生動吨瞎,所以我更喜歡將它拆開,變成 I/O multi plexing
multi意味著多穆咐,而plex意味著叢(叢:聚集颤诀,許多事物湊在一起。)庸娱,那么字面上來看I/O multiplexing 就是將多個I/O湊在一起着绊。就像下面這張圖的前半部分一樣,中間的那條線就是我們的單個線程熟尉,它通過記錄傳入的每一個I/O流的狀態(tài)來同時管理多個IO归露。

1.3 I/O多路復(fù)用的實(shí)現(xiàn)
  • 當(dāng)進(jìn)程調(diào)用select,進(jìn)程就會被阻塞
  • 此時內(nèi)核會監(jiān)視所有select負(fù)責(zé)的的socket斤儿,當(dāng)socket的數(shù)據(jù)準(zhǔn)備好后剧包,就立即返回恐锦。
  • 進(jìn)程再調(diào)用read操作,數(shù)據(jù)就會從內(nèi)核拷貝到進(jìn)程疆液。

其實(shí)多路復(fù)用的實(shí)現(xiàn)有多種方式:select一铅、poll、epoll

1.3.1 select實(shí)現(xiàn)方式
先理解一下select這個函數(shù)的形參都是什么

int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
  • nfds:指定待測試的描述子個數(shù)
  • readfds,writefds,exceptfds:指定了我們讓內(nèi)核測試讀堕油、寫和異常條件的描述字
  • fd_set:為一個存放文件描述符的信息的結(jié)構(gòu)體潘飘,可以通過下面的宏進(jìn)行設(shè)置。
void FD_ZERO(fd_set *fdset);
//清空集合
void FD_SET(int fd, fd_set *fdset);
//將一個給定的文件描述符加入集合之中
void FD_CLR(int fd, fd_set *fdset);
//將一個給定的文件描述符從集合中刪除
int FD_ISSET(int fd, fd_set *fdset);
// 檢查集合中指定的文件描述符是否可以讀寫

  • timeout:內(nèi)核等待指定的描述字中就緒的時間長度
  • 返回值:失敗-1 超時0 成功>0
#define FILE "/dev/input/mouse0"
int main(void)
{
 int fd = -1;
 int sele_ret = -1;
 fd_set Fd_set;
 struct timeval time = {0};
 char buf[10] = {0};

 //打開設(shè)備文件
 fd = open(FILE, O_RDONLY);
 if (-1 == fd)
{
      perror("open error");
      exit(-1);
}

//構(gòu)建多路復(fù)用IO
FD_ZERO(&Fd_set); //清除全部fd
FD_SET(0, &Fd_set); //添加標(biāo)準(zhǔn)輸入
FD_SET(fd, &Fd_set); //添加鼠標(biāo)
time.tv_sec = 10; //設(shè)置阻塞超時時間為10秒鐘
time.tv_usec = 0; 

sele_ret = select(fd+1, &Fd_set, NULL, NULL, &time);
if (0 > sele_ret)
{
    perror("select error");
    exit(-1);
}
else if (0 == sele_ret)
{
    printf("無數(shù)據(jù)輸入掉缺,等待超時.\n");
}
else
{
    if (FD_ISSET(0, &Fd_set)) //監(jiān)聽得到得到的結(jié)果若是鍵盤,則讓去讀取鍵盤的數(shù)據(jù)
{
    memset(buf, 0, sizeof(buf));
    read(0, buf, sizeof(buf)/2);
    printf("讀取鍵盤的內(nèi)容是: %s.\n", buf);
}

if (FD_ISSET(fd, &Fd_set)) //監(jiān)聽得到得到的結(jié)果若是鼠標(biāo),則去讀取鼠標(biāo)的數(shù)據(jù)
{
    memset(buf, 0, sizeof(buf));
    read(fd, buf, sizeof(buf)/2);
    printf("讀取鼠標(biāo)的內(nèi)容是: %s.\n", buf);
}
}

//關(guān)閉鼠標(biāo)設(shè)備文件
    close(fd);
    return 0;
}
1.3.2 poll實(shí)現(xiàn)方式

先理解一下poll這個函數(shù)的形參是什么

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • pollfd:又是一個結(jié)構(gòu)體
struct pollfd {
int fd; //文件描述符
short events; //請求的事件(請求哪種操作)
short revents; //返回的事件
};
  • nfds:指定待測試的描述子個數(shù)
  • timeout:內(nèi)核等待指定的描述字中就緒的時間長度
  • timeout:內(nèi)核等待指定的描述字中就緒的時間長度
#define FILE "/dev/input/mouse0"

int main(void)
{
    int fd = -1;
    int poll_ret = 0;
    struct pollfd poll_fd[2] = {0};
    char buf[100] = {0};

    //打開設(shè)備文件
    fd = open(FILE, O_RDONLY);
    if (-1 == fd)
    {
        perror("open error");
        exit(-1);
    }

    //構(gòu)建多路復(fù)用IO
    poll_fd[0].fd = 0; //鍵盤
    poll_fd[0].events = POLLIN; //定義請求的事件為讀數(shù)據(jù)
    poll_fd[1].fd = fd; //鼠標(biāo)
    poll_fd[1].events = POLLIN; //定義請求的事件為讀數(shù)據(jù)
    int time = 10000; //定義超時時間為10秒鐘

    poll_ret = poll(poll_fd, fd+1, time);
    if (0 > poll_ret)
    {
        perror("poll error");
        exit(-1);
    }
     else if (0 == poll_ret)
    {
        printf("阻塞超時.\n");
    }
    else
    {
        if (poll_fd[0].revents == poll_fd[0].events)
 //監(jiān)聽得到得到的結(jié)果若是鍵盤,則讓去讀取鍵盤的數(shù)據(jù)
        {
            memset(buf, 0, sizeof(buf));
            read(0, buf, sizeof(buf)/2);
            printf("讀取鍵盤的內(nèi)容是: %s.\n", buf);
        }

        if (poll_fd[1].revents == poll_fd[1].events) 
//監(jiān)聽得到得到的結(jié)果若是鼠標(biāo),則去讀取鼠標(biāo)的數(shù)據(jù)
        {
              memset(buf, 0, sizeof(buf));
              read(fd, buf, sizeof(buf)/2);
              printf("讀取鼠標(biāo)的內(nèi)容是: %s.\n", buf);
        }
  }
//關(guān)閉文件
close(fd);
return 0;
}
1.3.3 epoll實(shí)現(xiàn)方式

epoll操作過程中會用到的重要函數(shù)

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  • int epoll_create(int size):創(chuàng)建一個epoll的句柄卜录,size表示監(jiān)聽數(shù)目的大小。創(chuàng)建完句柄它會自動占用一個fd值眶明,使用完epoll一定要記得close艰毒,不然fd會被消耗完。
  • int epoll_ctl:這是epoll的事件注冊函數(shù)搜囱,和select不同的是select在監(jiān)聽的時候會告訴內(nèi)核監(jiān)聽什么樣的事件丑瞧,而epoll必須在epoll_ctl先注冊要監(jiān)聽的事件類型。
    它的第一個參數(shù)返回epoll_creat的執(zhí)行結(jié)果
    第二個參數(shù)表示動作蜀肘,用下面幾個宏表示

EPOLL_CTL_ADD:注冊新的fd到epfd中绊汹;
EPOLL_CTL_MOD:修改已經(jīng)注冊的fd的監(jiān)聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd幌缝;

第三參數(shù)為監(jiān)聽的fd,第四個參數(shù)是告訴內(nèi)核要監(jiān)聽什么事

  • int epoll_wait:等待事件的發(fā)生灸促,類似于select的調(diào)用

2. select

2.1 select函數(shù)的調(diào)用過程

a. 從用戶空間將fd_set拷貝到內(nèi)核空間
b. 注冊回調(diào)函數(shù)
c. 調(diào)用其對應(yīng)的poll方法
d. poll方法會返回一個描述讀寫是否就緒的mask掩碼诫欠,根據(jù)這個mask掩碼給fd_set賦值涵卵。
e. 如果遍歷完所有的fd都沒有返回一個可讀寫的mask掩碼,就會讓select的進(jìn)程進(jìn)入休眠模式荒叼,直到發(fā)現(xiàn)可讀寫的資源后轿偎,重新喚醒等待隊列上休眠的進(jìn)程。如果在規(guī)定時間內(nèi)都沒有喚醒休眠進(jìn)程被廓,那么進(jìn)程會被喚醒重新獲得CPU坏晦,再去遍歷一次fd。
f. 將fd_set從內(nèi)核空間拷貝到用戶空間

2.2 select函數(shù)優(yōu)缺點(diǎn)

缺點(diǎn):兩次拷貝耗時嫁乘、輪詢所有fd耗時昆婿,支持的文件描述符太小
優(yōu)點(diǎn):跨平臺支持

3. poll

3.1 poll函數(shù)的調(diào)用過程(與select完全一致)
3.2 poll函數(shù)優(yōu)缺點(diǎn)

優(yōu)點(diǎn):連接數(shù)(也就是文件描述符)沒有限制(鏈表存儲)
缺點(diǎn):大量拷貝,水平觸發(fā)(當(dāng)報告了fd沒有被處理蜓斧,會重復(fù)報告仓蛆,很耗性能)

4. epoll

4.1 epoll的ET與LT模式

LT:延遲處理,當(dāng)檢測到描述符事件通知應(yīng)用程序挎春,應(yīng)用程序不立即處理該事件看疙。那么下次會再次通知應(yīng)用程序此事件豆拨。
ET:立即處理,當(dāng)檢測到描述符事件通知應(yīng)用程序能庆,應(yīng)用程序會立即處理施禾。
ET模式減少了epoll被重復(fù)觸發(fā)的次數(shù),效率比LT高搁胆。我們在使用ET的時候弥搞,必須采用非阻塞套接口,避免某文件句柄在阻塞讀或阻塞寫的時候?qū)⑵渌募枋龇娜蝿?wù)餓死

4.2 epoll的函數(shù)調(diào)用流程

a. 當(dāng)調(diào)用epoll_wait函數(shù)的時候渠旁,系統(tǒng)會創(chuàng)建一個epoll對象拓巧,每個對象有一個evenpoll類型的結(jié)構(gòu)體與之對應(yīng),結(jié)構(gòu)體成員結(jié)構(gòu)如下一死。

rbn,代表將要通過epoll_ctl向epll對象中添加的事件肛度。這些事情都是掛載在紅黑樹中。
rdlist投慈,里面存放的是將要發(fā)生的事件

b. 文件的fd狀態(tài)發(fā)生改變承耿,就會觸發(fā)fd上的回調(diào)函數(shù)
c. 回調(diào)函數(shù)將相應(yīng)的fd加入到rdlist,導(dǎo)致rdlist不空伪煤,進(jìn)程被喚醒加袋,epoll_wait繼續(xù)執(zhí)行。
d. 有一個事件轉(zhuǎn)移函數(shù)——ep_events_transfer抱既,它會將rdlist的數(shù)據(jù)拷貝到txlist上职烧,并將rdlist的數(shù)據(jù)清空。
e. ep_send_events函數(shù)防泵,它掃描txlist的每個數(shù)據(jù)蚀之,調(diào)用關(guān)聯(lián)fd對應(yīng)的poll方法去取fd中較新的事件,將取得的事件和對應(yīng)的fd發(fā)送到用戶空間捷泞。如果fd是LT模式的話足删,會被txlist的該數(shù)據(jù)重新放回rdlist,等待下一次繼續(xù)觸發(fā)調(diào)用锁右。

4.3 epoll的優(yōu)點(diǎn)

1.沒有最大并發(fā)連接的限制
2.只有活躍可用的fd才會調(diào)用callback函數(shù)
3.內(nèi)存拷貝是利用mmap()文件映射內(nèi)存的方式加速與內(nèi)核空間的消息傳遞失受,減少復(fù)制開銷。(內(nèi)核與用戶空間共享一塊內(nèi)存)

只有存在大量的空閑連接和不活躍的連接的時候咏瑟,使用epoll的效率才會比select/poll高

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拂到,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子码泞,更是在濱河造成了極大的恐慌兄旬,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浦夷,死亡現(xiàn)場離奇詭異辖试,居然都是意外死亡辜王,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門罐孝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呐馆,“玉大人,你說我怎么就攤上這事莲兢⌒诶矗” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵改艇,是天一觀的道長收班。 經(jīng)常有香客問我,道長谒兄,這世上最難降的妖魔是什么摔桦? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮承疲,結(jié)果婚禮上邻耕,老公的妹妹穿的比我還像新娘。我一直安慰自己燕鸽,他們只是感情好兄世,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啊研,像睡著了一般御滩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上党远,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天削解,我揣著相機(jī)與錄音,去河邊找鬼麸锉。 笑死钠绍,一個胖子當(dāng)著我的面吹牛舆声,可吹牛的內(nèi)容都是我干的花沉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼媳握,長吁一口氣:“原來是場噩夢啊……” “哼碱屁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蛾找,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤娩脾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后打毛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柿赊,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡俩功,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碰声。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诡蜓。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胰挑,靈堂內(nèi)的尸體忽然破棺而出蔓罚,到底是詐尸還是另有隱情,我是刑警寧澤瞻颂,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布豺谈,位于F島的核電站,受9級特大地震影響贡这,放射性物質(zhì)發(fā)生泄漏茬末。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一盖矫、第九天 我趴在偏房一處隱蔽的房頂上張望团南。 院中可真熱鬧,春花似錦炼彪、人聲如沸吐根。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拷橘。三九已至,卻和暖如春喜爷,著一層夾襖步出監(jiān)牢的瞬間冗疮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工檩帐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留术幔,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓湃密,卻偏偏與公主長得像诅挑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子泛源,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容