Ⅰ epoll和多線程哪個好
多線程。
1、面向多核的伺服器編程時,多線程強於epoll,因為對於每個多進程來說,資源是獨立的,切換core的時候無需考慮上下文。
2、每個資源共享時以及在core切換的時候,多線程更好,多線程能夠直接進行共享,而epoll資源必須從一個core復制到另一個core才能繼續運算。
Ⅱ 關於linux下的select/epoll
select這個系統調用的原型如下
第一個參數nfds用來告訴內核 要掃描的socket fd的數量+1 ,select系統調用最大接收的數量是1024,但是如果每次都去掃描1024,實際上的數量並不多,則效率太低,這里可以指定需要掃描的數量。 最大數量為1024,如果需要修改這個數量,則需要重新編譯Linux內核源碼。
第2、3、4個參數分別是readfds、writefds、exceptfds,傳遞的參數應該是fd_set 類型的引用,內核會檢測每個socket的fd, 如果沒有讀事件,就將對應的fd從第二個參數傳入的fd_set中移除,如果沒有寫事件,就將對應的fd從第二個參數的fd_set中移除,如果沒有異常事件,就將對應的fd從第三個參數的fd_set中移除 。這里我們應該 要將實際的readfds、writefds、exceptfds拷貝一份副本傳進去,而不是傳入原引用,因為如果傳遞的是原引用,某些socket可能就已經丟失 。
最後一個參數是等待時間, 傳入0表示非阻塞,傳入>0表示等待一定時間,傳入NULL表示阻塞,直到等到某個socket就緒 。
FD_ZERO()這個函數將fd_set中的所有bit清0,一般用來進行初始化等。
FD_CLR()這個函數用來將bitmap(fd_set )中的某個bit清0,在客戶端異常退出時就會用到這個函數,將fd從fd_set中刪除。
FD_ISSET()用來判斷某個bit是否被置1了,也就是判斷某個fd是否在fd_set中。
FD_SET()這個函數用來將某個fd加入fd_set中,當客戶端新加入連接時就會使用到這個函數。
epoll_create系統調用用來創建epfd,會在開辟一塊內存空間(epoll的結構空間)。size為epoll上能關注的最大描述符數,不夠會進行擴展,size只要>0就行,早期的設計size是固定大小,但是現在size參數沒什麼用,會自動擴展。
返回值是epfd,如果為-1則說明創建epoll對象失敗 。
第一個參數epfd傳入的就是epoll_create返回的epfd。
第二個參數傳入對應操作的宏,包括 增刪改(EPOLL_CTL_ADD、EPOLL_CTL_DEL、EPOLL_CTL_MOD) 。
第三個參數傳入的是 需要增刪改的socket的fd 。
第四個參數傳入的是 需要操作的fd的哪些事件 ,具體的事件可以看後續。
返回值是一個int類型,如果為-1則說明操作失敗 。
第一個參數是epfd,也就是epoll_create的返回值。
第二個參數是一個epoll_event類型的指針,也就是傳入的是一個數組指針。 內核會將就緒的socket的事件拷貝到這個數組中,用戶可以根據這個數組拿到事件和消息等 。
第三個參數是maxevents,傳入的是 第二個參數的數組的容量 。
第四個參數是timeout, 如果設為-1一直阻塞直到有就緒數據為止,如果設為0立即返回,如果>0那麼阻塞一段時間 。
返回值是一個int類型,也就是就緒的socket的事件的數量(內核拷貝給用戶的events的元素的數量),通過這個數量可以進行遍歷處理每個事件 。
一般需要傳入 ev.data.fd 和 ev.events ,也就是fd和需要監控的fd的事件。事件如果需要傳入多個,可以通過按位與來連接,比如需要監控讀寫事件,只需要像如下這樣操作即可: ev.events=EPOLLIN | EPOLLOUT 。
LT(水平觸發), 默認 的工作模式, 事件就緒後用戶可以選擇處理和不處理,如果用戶不處理,內核會對這部分數據進行維護,那麼下次調用epoll_wait()時仍舊會打包出來 。
ET(邊緣觸發),事件就緒之後, 用戶必須進行處理 ,因為內核把事件打包出來之後就把對應的就緒事件給清掉了, 如果不處理那麼就緒事件就沒了 。ET可以減少epoll事件被重復觸發的次數,效率比LT高。
如果需要設置為邊緣觸發只需要設置事件為類似 ev.events=EPOLLIN | EPOLLET 即可 。
select/poll/epoll是nio多路復用技術, 傳統的bio無法實現C10K/C100K ,也就是無法滿足1w/10w的並發量,在這么高的並發量下,在進行上下文切換就很容易將伺服器的負載拉飛。
1.將fd_set從用戶態拷貝到內核態
2.根據fd_set掃描內存中的socket的fd的狀態,時間復雜度為O(n)
3.檢查fd_set,如果有已經就緒的socket,就給對應的socket的fd打標記,那麼就return 就緒socket的數量並喚醒當前線程,如果沒有就緒的socket就繼續阻塞當前線程直到有socket就緒才將當前線程喚醒。
4.如果想要獲取當前已經就緒的socket列表,則還需要進行一次系統調用,使用O(n)的時間去掃描socket的fd列表,將已經打上標記的socket的fd返回。
CPU在同一個時刻只能執行一個程序,通過RR時間片輪轉去切換執行各個程序。沒有被掛起的進程(線程)則在工作隊列中排隊等待CPU的執行,將進程(線程)從工作隊列中移除就是掛起,反映到Java層面的就是線程的阻塞。
什麼是中斷?當我們使用鍵盤、滑鼠等IO設備的時候,會給主板一個電流信號,這個電流信號就給CPU一個中斷信號,CPU執行完當前的指令便會保存現場,然後執行鍵盤/滑鼠等設備的中斷程序,讓中斷程序獲取CPU的使用權,在中斷程序後又將現場恢復,繼續執行之前的進程。
如果第一次沒檢測到就緒的socket,就要將其進程(線程)從工作隊列中移除,並加入到socket的等待隊列中。
socket包含讀緩沖區+寫緩沖區+等待隊列(放線程或eventpoll對象)
當從客戶端往伺服器端發送數據時,使用TCP/IP協議將通過物理鏈路、網線發給伺服器的網卡設備,網卡的DMA設備將接收到的的數據寫入到內存中的一塊區域(網卡緩沖區),然後會給CPU發出一個中斷信號,CPU執行完當前指令則會保存現場,然後網卡的中斷程序就獲得了CPU的使用權,然後CPU便開始執行網卡的中斷程序,將內存中的緩存區中的數據包拿出,判斷埠號便可以判斷它是哪個socket的數據,將數據包寫入對應的socket的讀(輸入)緩沖區,去檢查對應的socket的等待隊列有沒有等待著的進程(線程),如果有就將該線程(進程)從socket的等待隊列中移除,將其加入工作隊列,這時候該進程(線程)就再次擁有了CPU的使用許可權,到這里中斷程序就結束了。
之後這個進程(線程)就執行select函數再次去檢查fd_set就能發現有socket緩沖區中有數據了,就將該socket的fd打標記,這個時候select函數就執行完了,這時候就會給上層返回一個int類型的數值,表示已經就緒的socket的數量或者是發生了錯誤。這個時候就再進行內核態到用戶態的切換,對已經打標記的socket的fd進行處理。
將原本1024bit長度的bitmap(fd_set)換成了數組的方式傳入 ,可以 解決原本1024個不夠用的情況 ,因為傳入的是數組,長度可以不止是1024了,因此socket數量可以更多,在Kernel底層會將數組轉換成鏈表。
在十多年前,linux2.6之前,不支持epoll,當時可能會選擇用Windows/Unix用作伺服器,而不會去選擇Linux,因為select/poll會隨著並發量的上升,性能變得越來越低,每次都得檢查所有的Socket列表。
1.select/poll每次調用都必須根據提供所有的socket集合,然後就 會涉及到將這個集合從用戶空間拷貝到內核空間,在這個過程中很耗費性能 。但是 其實每次的socket集合的變化也許並不大,也許就1-2個socket ,但是它會全部進行拷貝,全部進行遍歷一一判斷是否就緒。
2.select/poll的返回類型是int,只能代表當前的就緒的socket的數量/發生了錯誤, 如果還需要知道是哪些socket就緒了,則還需要再次使用系統調用去檢查哪些socket是就緒的,又是一次O(n)的操作,很耗費性能 。
1.epoll在Kernel內核中存儲了對應的數據結構(eventpoll)。我們可以 使用epoll_create()這個系統調用去創建一個eventpoll對象 ,並返回eventpoll的對象id(epfd),eventpoll對象主要包括三個部分:需要處理的正在監聽的socket_fd列表(紅黑樹結構)、socket就緒列表以及等待隊列(線程)。
2.我們可以使用epoll_ctl()這個系統調用對socket_fd列表進行CRUD操作,因為可能頻繁地進行CRUD,因此 socket_fd使用的是紅黑樹的結構 ,讓其效率能更高。epoll_ctl()傳遞的參數主要是epfd(eventpoll對象id)。
3.epoll_wait()這個系統調用默認會 將當前進程(線程)阻塞,加入到eventpoll對象的等待隊列中,直到socket就緒列表中有socket,才會將該進程(線程)重新加入工作隊列 ,並返回就緒隊列中的socket的數量。
socket包含讀緩沖區、寫緩沖區和等待隊列。當使用epoll_ctl()系統調用將socket新加入socket_fd列表時,就會將eventpoll對象引用加到socket的等待隊列中, 當網卡的中斷程序發現socket的等待隊列中不是一個進程(線程),而是一個eventpoll對象的引用,就將socket引用追加到eventpoll對象的就緒列表的尾部 。而eventpoll對象中的等待隊列存放的就是調用了epoll_wait()的進程(線程),網卡的中斷程序執行會將等待隊列中的進程(線程)重新加入工作隊列,讓其擁有佔用CPU執行的資格。epoll_wait()的返回值是int類型,返回的是就緒的socket的數量/發生錯誤,-1表示發生錯誤。
epoll的參數有傳入一個epoll_event的數組指針(作為輸出參數),在調用epoll_wait()返回的同時,Kernel內核還會將就緒的socket列表添加到epoll_event類型的數組當中。
Ⅲ 多線程處理epoll的常用方式
先說epoll 官方文檔的建議,在ET模式下,如果用多線程epoll_wait 同一個epoll-fd,那麼當其監聽fd產生了事件,此時理論上所有的線程都會收到通知,這將導致群驚,因此epoll只會喚醒一個線程來處理!
但是,此時如果fd又觸發了新的事件,那麼就會喚醒新的線程!這將會導致多個線程操作同一個fd,這不是推薦的方式,可能導致線程安全問題!
解決方案是使用EPOLLNESHOT標志,即在一次wait返回後禁止fd再產生事件,並在處理完成後使用epoll_ctl的MOD操作重新開啟。
但是在webServer的實現裡面,一般是這樣的:
一個線程負責監聽TCP鏈接,當收到用戶的TCP鏈接後,創建client-fd ,然後從線程池中挑選一個線程,將client -fd添加到工作線程的epoll中,當client-fd收到HTTP請求,由工作線程處理之。
換句話說,每個線程都處理自己的fd,從而完全避免多線程安全問題,當然了上述操作主要是為了高並發,它有個熟悉的名字,Reactor模型。
Pistache就是上述工作模式。
Ⅳ 請教關於多線程epoll
當一個節點和多個節點建立連接時,如何高效的處理多個連接的數據,下面具體分析兩者的區別。1.select函數函數原型:intselect(intnfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout);參數介紹:(1)nfds--fdset集合中最大描述符值加1(2)fdset--一個位數組,其大小限制為_FD_SETSIZE(1024)位數組的每一位代表的是其對應的描述符是否需要被檢查。(3)readfds--讀事件文件描述符數組(4)writefds--寫事件文件描述符數組(5)exceptfds--錯誤事件文件描述符數組(6)timeout--超時事件,該結構被內核修改,其值為超時剩餘時間。對應內核:select對應於內核中的sys_select調用,sys_select首先將第二三四個參數指向的fd_set拷貝到內核,然後對每個被SET的描述符調用進行poll,並記錄在臨時結果中(fdset),如果有事件發生,select會將臨時結果寫到用戶空間並返回;當輪詢一遍後沒有任何事件發生時,如果指定了超時時間,則select會睡眠到超時,睡眠結束後再進行一次輪詢,並將臨時結果寫到用戶空間,然後返2.select/poll特點傳統的select/poll每次調用都會線性掃描全部的集合,導致效率呈現線性下降。poll的執行分三部分:(1).將用戶傳入的pollfd數組拷貝到內核空間,因為拷貝操作和數組長度相關,時間上這是一個O(n)操作(2).查詢每個文件描述符對應設備的狀態,如果該設備尚未就緒,則在該設備的等待隊列中加入一項並繼續查詢下一設備的狀態。查詢完所有設備後如果沒有一個設備就緒,這時則需要掛起當前進程等待,直到設備就緒或者超時。設備就緒後進程被通知繼續運行,這時再次遍歷所有設備,以查找就緒設備。這一步因為兩次遍歷所有設備,時間復雜度也是O(n),這裡面不包括等待時間(3).將獲得的數據傳送到用戶空間並執行釋放內存和剝離等待隊列等善後工作,向用戶空間拷貝數據與剝離等待隊列等操作的的時間復雜度同樣是O(n)。3.epoll機制Linux2.6內核完全支持epoll。epoll的IO效率不隨FD數目增加而線性下降。要使用epoll只需要這三個系統調用:epoll_create(2),epoll_ctl(2),epoll_wait(2)epoll用到的所有函數都是在頭文件sys/epoll.h中聲明的,內核實現中epoll是根據每個fd上面的callback函數實現的。只有"活躍"的socket才會主動的去調用callback函數,其他idle狀態socket則不會。如果所有的socket基本上都是活躍的---比如一個高速LAN環境,過多使用epoll,效率相比還有稍微的下降。但是一旦使用idleconnections模擬WAN環境,epoll的效率就遠在select/poll之上了。3.1所用到的函數:(1)、intepoll_create(intsize)該函數生成一個epoll專用的文件描述符,其中的參數是指定生成描述符的最大范圍(2)、intepoll_ctl(intepfd,intop,intfd,structepoll_event*event)用於控制某個文件描述符上的事件,可以注冊事件,修改事件,刪除事件。如果調用成功返回0,不成功返回-1intepoll_ctl{intepfd,//由epoll_create生成的epoll專用的文件描述符intop,//要進行的操作例如注冊事件,可能的取值EPOLL_CTL_ADD注冊、//EPOLL_CTL_MOD修改、EPOLL_CTL_DEL刪除intfd,//關聯的文件描述符structepoll_event*event//指向epoll_event的指針}(3)、intepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout)用於輪詢I/O事件的發生,返回發生事件數intepoll_wait{intepfd,//由epoll_create生成的epoll專用的文件描述符structepoll_event*events,//用於回傳代處理事件的數組intmaxevents,//每次能處理的事件數inttimeout//等待I/O事件發生的超時值//為0的時候表示馬上返回,為-1的時候表示一直等下去,直到有事件//為任意正整數的時候表示等這么長的時間,如果一直沒有事件//一般如果網路主循環是單獨的線程的話,可以用-1來等,這樣可以保證一些效率//如果是和主邏輯在同一個線程的話,則可以用0來保證主循環的效率}epoll是為處理大批量句柄而作了改進的poll。4.epoll的優點:支持一個進程打開大數目的socket描述符(FD)select最不能忍受的是一個進程所打開的FD是有一定限制的,由FD_SETSIZE設置,默認值是2048。對於那些需要支持的上萬連接數目的IM伺服器來說顯然太少了。這時候可以:(1)可以修改這個宏然後重新編譯內核,不過資料也同時指出,這樣也會帶來網路效率的下降(2)可以選擇多進程的解決方案,不過雖然linux上創建進程的代價比較下,但是仍舊是不可忽視的,所以也不是很完美的方案epoll沒有這樣的限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大於2048,具體數組可以查看cat/proc/sys/fs/file-max查看,這個數目和系統內存關系很大。IO效率不隨FD數目增加而線性下降傳統的select/poll另一個致命弱點就是當你擁有一個很大的socket集合,不過由於網路延時,任一時間只有部分的socket是"活躍"的,但是select/poll每次調用都會線性掃描全部的集合,導致效率呈現線性下降。epoll不存在這個問題,它只會對「活躍」的socket進行操作。這是因為在內核實現中epoll是根據每個fd上面的callback函數實現的。那麼,只有"活躍"的socket才會主動的去調用callback函數,其他idle狀態socket則不會,在這點上,epoll實現了一個"偽"AIO,因為這時候推動力在os內核。在一些benchmark中,如果所有的socket基本上都是活躍的---比如一個高速LAN環境,epoll並不比select/poll有什麼效率,相反,如果過多使用epoll_ctl,效率相比還有稍微的下降。但是一旦使用idleconnections模擬WAN環境,epoll的效率就遠在select/poll之上了。使用mmap加速內核與用戶空間的消息傳遞這點實際上涉及到epoll的具體實現了。無論是select,poll還是epoll都需要內核把FD消息通知給用戶空間,如何避免不必要的內存拷貝就很重要,在這點上,epoll是通過內核於用戶空間mmap同一塊內存實現的。而如果你想我一樣從2.5內核就關注epoll的話,一定不會忘記手工mmap這一步的。內核微調這一點其實不算epoll的優點了,而是整個linux平台的優點。也許你可以懷疑linux平台,但是你無法迴避linux平台賦予你微調內核的能力。比如,內核TCP/IP協議棧使用內存池管理sk_buff結構,那麼可以在運行時期動態調整這個內存pool(skb_head_pool)的大小---通過echoXXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函數的第2個參數(TCP完成3次握手的數據包隊列長度),也可以根據你平台內存大小動態調整。更甚至在一個數據包面數目巨大但同時每個數據包本身大小卻很小的特殊系統上嘗試最新的NAPI網卡驅動架構。
Ⅳ linux並發伺服器中epoll+多線程分別怎麼理
某個線程處理某個特定事件吧
通過epoll檢測一些事件,事件觸發時,創建一個線程來專職處理這個事件
Ⅵ poll,select,epoll是多線程實現的嗎
在Linux Socket伺服器短編程時,為了處理大量客戶的連接請求,需要使用非阻塞I/O和復用,select、poll 和epoll是Linux API提供的I/O復用方式,自從Linux 2.6中加入了epoll之後
Ⅶ Linux開發,使用多線程還是用IO復用select/epoll
多線程和用select/epoll是沒有關聯的,在select和epoll模型里也可以使用多線程進行io處理,select/epoll
的出現是為了解決一個線程對應一個請求時阻塞線程的問題,基於epoll的事件模型,解決了線程的阻塞問題即一個線程可以為多個請求服務
Ⅷ linux手冊翻譯——epoll(7)
epoll — I/O 事件通知機制
epoll API與poll具有相同功能:監視多個文件描述符,以查看這些文件描述符中任何一個上可以進行特定的I/O操作,如是否可讀/可寫。epoll API可以使用edge-triggered和level-triggered兩種介面,並且可以高性能的同時監視大量的fd,這是對epoll相對魚poll的核心優勢。
epoll的核心概念是epoll instance,這是一種內核數據結構,從用戶空間角度看,可以視為一個包含兩種列表的容器:
提供以下3個系統調用來創建和管理epoll instance:
兩種觸發模式:level_triggered (LT)和 edge_triggered(ET)
假設發生如下場景:
如果使用ET觸發,那麼步驟5就會阻塞掛起,這是因為對於ET模式而言,只有當緩沖區數據發生變化時才會觸發事件(對於讀,「變化」指新數據到達)。而對於LT而言,只要緩沖區中存在數據,就會一直觸發。
使用ET時應使用非阻塞的fd (即無法讀寫時返回EAGIN,而非阻塞),以避免task阻塞導教其他fd無法監控。
合理使用ET模式步驟:
1)修改fd為非阻塞(non-blocking)
2)在read或write操作返回EAGIN後再執行wait等待事件。
為何ET需要非阻塞呢?因為ET模式下要循環多次read,並通過阻塞(即是否返回EAGIN)來確定數據是否全部讀完。第一次執行read是不可能阻塞的。
若使用LT模式(默認情況下,使用ET模式),則可以將epoll看作是一個快速的poll,可以在任何地方使用epoll(LT)替換poll,因為他們的語義完全相同。
即使採用ET模式,在多線程的情況依然會導致產生多個事件(對於同一被監控的fd),這將導致多個線程操作同一fd,可以使用EPOLLNESHOT標志避免,即在一次wait返回後禁止fd再產生事件,並在處理完成後使用epoll_ctl的MOD操作重新開啟。
在多進程或多線程中,epoll_fd是共享的,這將導致所有線程都會知道事情的發生,但是epoll僅會喚醒一個線程,以規避「群驚」現象。
If the system is in autosleep mode via /sys/power/autosleep and an event happens which wakes the device from sleep, the device driver will keep the device awake only until that event is queued. To keep the device awake until the event has been processed, it is necessary to use the epoll_ctl(2) EPOLLWAKEUP flag.
When the EPOLLWAKEUP flag is set in the events field for a struct epoll_event, the system will be kept awake from the moment the event is queued, through the epoll_wait(2) call which returns the event until the subsequent epoll_wait(2) call. If the event should keep the system awake beyond that time, then a separate wake_lock should be taken before the second epoll_wait(2) call.
以下介面可用於限制 epoll 消耗的內核內存用量:
雖然 epoll 在用作級別觸發介面時具有與 poll(2) 相同的語義,但邊緣觸發的用法需要更多說明以避免應用程序事件循環中的阻塞。
在下面例子中,listener 是一個非阻塞套接字,在它上面調用了 listen(2)。 函數 do_use_fd() 使用新的就緒文件描述符,直到 read(2) 或 write(2) 返回 EAGAIN。 事件驅動的狀態機應用程序應該在收到 EAGAIN 後記錄其當前狀態,以便在下一次調用 do_use_fd() 時,它將繼續從之前停止的位置read (2) 或write (2)。
當使用ET模式時,出於性能原因,可以通過EPOLL_CTL_ADD調用 epoll_ctl(2)指定 (EPOLLIN|EPOLLOUT)添加一次文件描述符。 避免使用 EPOLL_CTL_MOD 調用 epoll_ctl(2)在 EPOLLIN 和 EPOLLOUT 之間連續切換。
The epoll API is Linux-specific. Some other systems provide similar mechanisms, for example, FreeBSD has kqueue, and Solaris has /dev/poll.
通過 epoll 文件描述符監視的文件描述符集可以通過進程的 /proc/[pid]/fdinfo 目錄中的 epoll 文件描述符條目查看。 有關更多詳細信息,請參閱 proc(5)。
kcmp(2) KCMP_EPOLL_TFD 操作可用於測試文件描述符是否存在於 epoll 實例中。