nginx不是使用epoll么? epoll貌似是同步的吧! 那nginx的非同步非阻塞到底非同步在哪裡?

nginx不是使用epoll么? epoll貌似是同步的吧! 那nginx所謂的非同步非阻塞到底非同步在哪裡?

還有就是unix上說 select/poll/epoll 管理的socket一般設置成非阻塞, 我覺著這裡射不設置為非阻塞好像沒有什麼關係吧, 畢竟select/poll/epoll本身都會阻塞用戶進程的嘛! 如果設置為非阻塞socket的話, select/poll需要輪詢, 但是我設置為阻塞socket的話,select直接阻塞不就行了; 反正兩種情況都會造成調用select的用戶進程阻塞啊

其實知乎里的大神 陳碩大大已經明確說明了epol是同步了, 但是其實有個大神叫韓天峰, 他卻說epoll是非同步非阻塞的韓天峰(Rango)的博客 quot; 搜索結果 quot; epoll


靈劍:怎樣理解阻塞非阻塞與同步非同步的區別?

另外很多東西動手寫寫程序就明白了,別光紙上談兵。select/poll/epoll的意義在於同時等待多個socket上的活動。select/poll/epoll永遠都是阻塞的,跟socket是否阻塞無關。


首先,epoll的調用的確是同步的。

要理解nginx的非同步,首先要知道nginx的事件驅動框架是如何運作的。

nginx主要分為網路事件和定時器事件,而網路事件又以TCP為主。網路事件主要包含read/write事件,而定時器事件就一個超時事件這個沒什麼好說的。

nginx的網路事件是不需要創建的,nginx在初始化的時候會預分配出所有的網路read/write事件,每當有新連接到來時,就會把read/write事件跟對應的socket關聯起來,然後放入epoll事件隊列中。timer事件由模塊開發者自行創建然後通過相關的API投入到epoll事件隊列中。

然後,nginx會一直(阻塞)等待epoll返回事件通知或者epoll_wait超時,一旦有事件觸發,nginx就會調用關聯的(read/write)handler處理事件。這裡重點來了,開發者必須保證每一個事件handler都不得包含任何阻塞調用。否則,nginx worker的主線程將會因為一個事件阻塞,導致隊列裡面可能還有一大堆事件不能及時處理,這會嚴重影響nginx的效率。這也就回答了題主為什麼socket不能設置為阻塞的原因,如果socket是阻塞的,那麼一個socket的IO事件就會阻塞後續所有的事件處理,CPU就會空轉,等在那裡沒事幹了。而在socket非阻塞調用期間,nginx可以繼續處理其他的事件。

這也是nginx把一個請求劃分為多個階段處理的原因之一。

以上就是nginx非同步非阻塞的實現,它形容的是nginx事件處理流程,而不是對epoll的調用。它有一個很明顯的缺點就是:nginx的非同步非阻塞完全依賴開發者來保證。倘若有一個很水的開發者開發了一個第三方模塊,而這個模塊里調用了阻塞的API,或者是高CPU運算操作,那麼就會拖累整個nginx進程。

以下是nginx worker的事件循環代碼:

for ( ;; ) {

if (ngx_exiting) {
if (ngx_event_no_timers_left() == NGX_OK) {
ngx_log_error(NGX_LOG_NOTICE, cycle-&>log, 0, "exiting");
ngx_worker_process_exit(cycle);
}
}

ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle-&>log, 0, "worker cycle");

ngx_process_events_and_timers(cycle);

if (ngx_terminate) {
ngx_log_error(NGX_LOG_NOTICE, cycle-&>log, 0, "exiting");
ngx_worker_process_exit(cycle);
}

if (ngx_quit) {
ngx_quit = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle-&>log, 0,
"gracefully shutting down");
ngx_setproctitle("worker process is shutting down");

if (!ngx_exiting) {
ngx_exiting = 1;
ngx_set_shutdown_timer(cycle);
ngx_close_listening_sockets(cycle);
ngx_close_idle_connections(cycle);
}
}

if (ngx_reopen) {
ngx_reopen = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle-&>log, 0, "reopening logs");
ngx_reopen_files(cycle, -1);
}
}

非常簡單,nginx worker初始化之後的整一個生命周期都在這個循環里。ngx_process_events_and_timers 就是事件處理函數。每循環一次都會處理一波事件,或者是epoll_wait超時。

還有就是unix上說 select/poll/epoll 管理的socket一般設置成非阻塞, 我覺著這裡射不設置為非阻塞好像沒有什麼關係吧, 畢竟select/poll/epoll本身都會阻塞用戶進程的嘛!

select/poll同理,若是中間穿插了阻塞操作,就會拖累其他時間處理。

最後舉個形象點的栗子吧:

題主你在知乎提出了這個問題之後,你是啥事都不幹不想,每時每刻都盯著知乎等著有人回答才去干其他事呢?還是先去干其他事,等知乎推送通知了再來看呢?


你的線程所有的函數都不阻塞,那麼祝你幸福,線程cpu佔用率就飆到100%了唄。哪怕一個請求都沒有。。當然不是沒有這樣的場景,只是應該一般人接觸不到。


當有多個IO要讀寫的時候,如果是阻塞模式,就需要為每個IO開一個線程,然後每個線程都去讀,然後阻塞。如果用select、poll、epoll,只需要一個線程阻塞就可以了,在通信不頻繁的時候,一個線程就夠了。如果多個IO之間有關聯,這樣做還可以避免多個線程間的資源競爭。
當然select/poll/epoll本身是阻塞的,和IO是否阻塞無關。很多時候我也會把IO配置為阻塞的,然後用select之類的去管理。


epoll 不是阻塞的,也不是非阻塞的。
阻塞這個詞是來形容io發送過程的,你call一個send,扔到系統緩衝區就返回,這是非阻塞;等它發完再返回,這是阻塞。
而epoll是等待fd上的事件,你告訴epoll要接收fd上的哪些個事件,事件來了,它就留著,到你epollwait的時候發給你。等待時間設成0就立刻返回,不管有有沒有事件。如果沒事件觸發,等待時間又不是0,那就在等待時間內,有事件了,把fd給你,沒有,超時返回。


底層是同步調用,上層邏輯是非同步調用,沒毛病。


看你的分析你怕是沒怎麼寫過用過這些東西,多寫寫吧,光說不練都是假的朋友...還有我覺得這種詞,少說,又不是沒有條件,多練練你就不用這種詞了。程序員應該嚴謹點.


nginx 不僅僅可以使用 epoll。

通常一個請求無法在一次 epoll 調度下就完成,比如往下游吐響應的時候,寫緩衝區不足,此時就需要將該請求的寫事件加回到 epoll,等下次可寫時觸發事件回調。因此往往一個請求需要多次調度才能完成。

在一個請求的事件加回到 epoll 後,nginx 轉而去處理其他的請求。我認為 nginx 的非同步體現在這裡。

nginx 的所有網路 IO 操作,都使用了非阻塞的套接字,所以 nginx 的非阻塞應該體現在這裡

當然,nginx 也有 AIO,這裡也體現了非同步的過程。


推薦閱讀:

DNS劫持和HTTP劫持有什麼區別?
怎樣算得上熟悉 TCP/IP 協議編程?
不同結構的網路之間的關係是怎樣的?
MQTT比TCP協議好在哪兒?

TAG:Unix | 計算機網路 | Nginx | epoll | Socket |