乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

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

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

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

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

       

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

      a)  iocp 是完全線程安全的,即同時可以有多個線程等待在 iocp 的完成隊列上;

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

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

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

        就可以允許等待在這個 mutex 上的其它線程中的一個進入 epoll_wait 從而擔任新的 leader。

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

       

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

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

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

        而不用擔心多線程問題。這個在 man 手冊頁也有描述(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ā)方式,一種是默認的水平觸發(fā)(LT)模式,即只要有可讀的數(shù)據(jù),就一直觸發(fā)讀事件;

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

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

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

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

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

       

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

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

          有可能在第 N 次讀取的數(shù)據(jù)之前送達給應用層。這是因為線程的調(diào)度導致的,雖然第 N+1 次數(shù)據(jù)只有在第 N 次數(shù)據(jù)

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

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

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

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

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

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

       

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

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

       

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

        第一次 send 會返回 ECONNRESET(104),再 send 會直接導致進程退出。如果捕捉該信號后,則第二次 send 會返回 EPIPE(32)。

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

       

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

        但是不要使用 socket 句柄作為這個映射的 key,因為當一個連接中斷而又有一個新的連接到來時,linux 上傾向于用最小的

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

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

        新連接(清理時 close 同樣的 fd 導致新分配的連接中斷)……而在 win32 上不存在這樣的情況,這并不是因為 winsock 比 bsdsock 做的更好,

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

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

       

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

        { fd, local_port, remote_port }

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

       

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

        這個會導致客戶端在不停斷開重連過程中積累大量的未釋放對象,時間長了有可能導致資源不足從而崩潰。

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

       

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

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

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

      但是我要說的是,千萬不要直接處理數(shù)據(jù),因為當你處理完之后,完成端口依舊會在之后進行通知,導致同一個數(shù)據(jù)被處理多次的情況。

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

       

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

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多