小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

[apue] epoll 的一些不為人所注意的特性

 python_lover 2022-06-22 發(fā)布于北京

之前曾經(jīng)使用 epoll 構(gòu)建過一個(gè)輕量級(jí)的 tcp 服務(wù)框架:

一個(gè)工業(yè)級(jí)、跨平臺(tái)、輕量級(jí)的 tcp 網(wǎng)絡(luò)服務(wù)框架:gevent

 

在調(diào)試的過程中,發(fā)現(xiàn)一些 epoll 之前沒怎么注意到的特性。

a)  iocp 是完全線程安全的,即同時(shí)可以有多個(gè)線程等待在 iocp 的完成隊(duì)列上;

  而 epoll 不行,同時(shí)只能有一個(gè)線程執(zhí)行 epoll_wait 操作,因此這里需要做一點(diǎn)處理,

  網(wǎng)上有人使用 condition_variable + mutex 實(shí)現(xiàn) leader-follower 線程模型,但我只用了一個(gè) mutex 就實(shí)現(xiàn)了,

  當(dāng)有事件發(fā)生了,leader 線程在執(zhí)行事件處理器之前 unlock  這個(gè) mutex,

  就可以允許等待在這個(gè) mutex 上的其它線程中的一個(gè)進(jìn)入 epoll_wait 從而擔(dān)任新的 leader。

 ?。ú恢蓝嗉右粋€(gè) cv 有什么用,有明白原理的提示一下哈)

 

b)  epoll 在加入、刪除句柄時(shí)是可以跨線程的,而且這一操作是線程安全的。

  之前一直以為 epoll 會(huì)像 select 一像,添加或刪除一個(gè)句柄需要先通知 leader 從 epoll_wait 中醒來,

  在重新 wait 之前通過  epoll_ctl 添加或刪除對(duì)應(yīng)的句柄。但是現(xiàn)在看完全可以在另一個(gè)線程中執(zhí)行 epoll_ctl 操作

  而不用擔(dān)心多線程問題。這個(gè)在 man 手冊(cè)頁也有描述(man epoll_wait):

NOTES
       While one thread is blocked in a call to epoll_pwait(), it is possible for  another  thread  to
       add  a  file  descriptor to the waited-upon epoll instance.  If the new file descriptor becomes
       ready, it will cause the epoll_wait() call to unblock.

       For a discussion of what may happen if a file descriptor in an epoll instance  being  monitored
       by epoll_wait() is closed in another thread, see select(2).

 

 c)  epoll 有兩種事件觸發(fā)方式,一種是默認(rèn)的水平觸發(fā)(LT)模式,即只要有可讀的數(shù)據(jù),就一直觸發(fā)讀事件;

  還有一種是邊緣觸發(fā)(ET)模式,即只在沒有數(shù)據(jù)到有數(shù)據(jù)之間觸發(fā)一次,如果一次沒有讀完全部數(shù)據(jù),

  則也不會(huì)再次觸發(fā),除非所有數(shù)據(jù)被讀完,且又有新的數(shù)據(jù)到來,才觸發(fā)。使用 ET 模式的好處是,

  不用在每次執(zhí)行處理器前將句柄從 epoll 移除、在執(zhí)行完之后再加入 epoll 中,

 ?。ㄈ绻贿@樣做的話,下一個(gè)進(jìn)來的 leader 線程還會(huì)認(rèn)為這個(gè)句柄可讀,從而導(dǎo)致一個(gè)連接的數(shù)據(jù)被多個(gè)線程同時(shí)處理)

  從而導(dǎo)致頻繁的移除、添加句柄。好多網(wǎng)上的 epoll 例子也推薦這種方式。但是我在親自驗(yàn)證后,發(fā)現(xiàn)使用 ET 模式有兩個(gè)問題:

 

  1)如果連接上來了大量數(shù)據(jù),而每次只能讀取部分(緩存區(qū)限制),則第 N 次讀取的數(shù)據(jù)與第 N+1 次讀取的數(shù)據(jù),

    有可能是兩個(gè)線程中執(zhí)行的,在讀取時(shí)它們的順序是可以保證的,但是當(dāng)它們通知給用戶時(shí),第 N+1 次讀取的數(shù)據(jù)

    有可能在第 N 次讀取的數(shù)據(jù)之前送達(dá)給應(yīng)用層。這是因?yàn)榫€程的調(diào)度導(dǎo)致的,雖然第 N+1 次數(shù)據(jù)只有在第 N 次數(shù)據(jù)

    讀取完之后才可能產(chǎn)生,但是當(dāng)?shù)?N+1 次數(shù)據(jù)所在的線程可能先于第 N 次數(shù)據(jù)所在的線程被調(diào)度,上述場(chǎng)景就會(huì)產(chǎn)生。

    這需要細(xì)心的設(shè)計(jì)讀數(shù)據(jù)到給用戶之間的流程,防止線程搶占(需要加一些保證順序的鎖);

  2)當(dāng)大量數(shù)據(jù)發(fā)送結(jié)束時(shí),連接中斷的通知(on_error)可能早于某些數(shù)據(jù)(on_read)到達(dá),其實(shí)這個(gè)原理與上面類似,

    就是客戶端在所有數(shù)據(jù)發(fā)送完成后主動(dòng)斷開連接,而獲取連接中斷的線程可能先于末尾幾個(gè)數(shù)據(jù)所在的線程被調(diào)度,

    從而在應(yīng)用層造成混亂(on_error 一般會(huì)刪除事件處理器,但是 on_read 又需要它去做回調(diào),好的情況會(huì)造成一些

    數(shù)據(jù)丟失,不好的情況下直接崩潰)

 

  鑒于以上兩點(diǎn),最后我還是使用了默認(rèn)的 LT 觸發(fā)模式,幸好有 b) 特性,我僅僅是增加了一些移除、添加的代碼,

  而且我不用在應(yīng)用層加鎖來保證數(shù)據(jù)的順序性了。

 

d)  一定要捕捉 SIGPIPE 事件,因?yàn)楫?dāng)某些連接已經(jīng)被客戶端斷開時(shí),而服務(wù)端還在該連接上 send 應(yīng)答包時(shí):

  第一次 send 會(huì)返回 ECONNRESET(104),再 send 會(huì)直接導(dǎo)致進(jìn)程退出。如果捕捉該信號(hào)后,則第二次 send 會(huì)返回 EPIPE(32)。

  這樣可以避免一些莫名其妙的退出問題(我也是通過 gdb 掛上進(jìn)程才發(fā)現(xiàn)是這個(gè)信號(hào)導(dǎo)致的)。

 

e)  當(dāng)管理多個(gè)連接時(shí),通常使用一種 map 結(jié)構(gòu)來管理 socket 與其對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)(特別是回調(diào)對(duì)象:handler)。

  但是不要使用 socket 句柄作為這個(gè)映射的 key,因?yàn)楫?dāng)一個(gè)連接中斷而又有一個(gè)新的連接到來時(shí),linux 上傾向于用最小的

  fd 值為新的 socket 分配句柄,大部分情況下,它就是你剛剛 close 或客戶端中斷的句柄。這樣一來很容易導(dǎo)致一些混亂的情況。

  例如新的句柄插入失?。ㄒ?yàn)榕f的雖然已經(jīng)關(guān)閉但是還未來得及從 map  中移除)、舊句柄的清理工作無意間關(guān)閉了剛剛分配的

  新連接(清理時(shí) close 同樣的 fd 導(dǎo)致新分配的連接中斷)……而在 win32 上不存在這樣的情況,這并不是因?yàn)?winsock 比 bsdsock 做的更好,

  相同的, winsock 也存在新分配的句柄與之前剛關(guān)閉的句柄一樣的場(chǎng)景(當(dāng)大量客戶端不停中斷重連時(shí));而是因?yàn)?iocp 基于提前

  分配的內(nèi)存塊作為某個(gè) IO 事件或連接的依據(jù),而 map 的 key 大多也依據(jù)這些內(nèi)存地址構(gòu)建,所以一般不存在重復(fù)的情況(只要還在 map 中就不釋放對(duì)應(yīng)內(nèi)存)。

 

  經(jīng)過觀察,我發(fā)現(xiàn)在 linux 上,即使新的連接占據(jù)了舊的句柄值,它的端口往往也是不同的,所以這里使用了一個(gè)三元組作為 map 的 key:

  { fd, local_port, remote_port }

  當(dāng) fd 相同時(shí),local_port 與 remote_port 中至少有一個(gè)是不同的,從而可以區(qū)分新舊連接。

 

f)  如果連接中斷或被對(duì)端主動(dòng)關(guān)閉連接時(shí),本端的 epoll 是可以檢測(cè)到連接斷開的,但是如果是自己 close 掉了 socket 句柄,則 epoll 檢測(cè)不到連接已斷開。

  這個(gè)會(huì)導(dǎo)致客戶端在不停斷開重連過程中積累大量的未釋放對(duì)象,時(shí)間長(zhǎng)了有可能導(dǎo)致資源不足從而崩潰。

  目前還沒有找到產(chǎn)生這種現(xiàn)象的原因,Windows 上沒有這種情況,有清楚這個(gè)現(xiàn)象原因的同學(xué),不吝賜教啊

 

最后,再亂入一波 iocp 的特性:

iocp 在異步事件完成后,會(huì)通過完成端口完成通知,但在某些情況下,異步操作可以“立即完成”,

就是說雖然只是提交異步事件,但是也有可能這個(gè)操作直接完成了。這種情況下,可以直接處理得到的數(shù)據(jù),相當(dāng)于是同步調(diào)用。

但是我要說的是,千萬不要直接處理數(shù)據(jù),因?yàn)楫?dāng)你處理完之后,完成端口依舊會(huì)在之后進(jìn)行通知,導(dǎo)致同一個(gè)數(shù)據(jù)被處理多次的情況。

所以最好的實(shí)踐就是,不論是否立即完成,都交給完成端口去處理,保證數(shù)據(jù)的一次性。

 

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

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多