两个线程同时从服务器接收消息_一文看懂I/O多路复用技术(mysql线程池)
概述
當我們要編寫一個echo服務器程序的時候,需要對用戶從標準輸入鍵入的交互命令做出響應。在這種情況下,服務器必須響應兩個相互獨立的I/O事件:1)網絡客戶端發起網絡連接請求,2)用戶在鍵盤上鍵入命令行。我們先等待哪個事件呢?沒有哪個選擇是理想的。如果在acceptor中等待一個連接請求,我們就不能響應輸入的命令。類似地,如果在read中等待一個輸入命令,我們就不能響應任何連接請求。針對這種困境的一個解決辦法就是I/O多路復用技術。基本思路就是使用select函數,要求內核掛起進程,只有在一個或多個I/O事件發生后,才將控制返回給應用程序。 --《UNIX網絡編程》
mysql線程池,就是I/O多路復用的體現。
參考:https://blog.csdn.net/wangxindong11/article/details/78591308一、I/O多路復用概述
I/O多路復用,I/O就是指的我們網絡I/O,多路指多個TCP連接(或多個Channel),復用指復用一個或少量線程。串起來理解就是很多個網絡I/O復用一個或少量的線程來處理這些連接。
多路復用的本質是同步非阻塞I/O,多路復用的優勢并不是單個連接處理的更快,而是在于能處理更多的連接。
I/O編程過程中,需要同時處理多個客戶端接入請求時,可以利用多線程或者I/O多路復用技術進行處理。
I/O多路復用技術通過把多個I/O的阻塞復用到同一個select阻塞上,一個進程監視多個描述符,一旦某個描述符就位, 能夠通知程序進行讀寫操作。因為多路復用本質上是同步I/O,都需要應用程序在讀寫事件就緒后自己負責讀寫。
最大的優勢是系統開銷小,不需要創建和維護額外線程或進程。
- 應用場景
- 服務器需要同時處理多個處于監聽狀態或者多個連接狀態的套接字
- 需要同時處理多種網絡協議的套接字
- 一個服務器處理多個服務或協議
目前支持多路復用的系統調用有select, poll, epoll。
二、幾種常用I/O模型
BIO
阻塞同步I/O模型,服務器需要監聽端口號,客戶端通過IP和端口與服務器簡歷TCP連接,以同步阻塞的方式傳輸數據。服務端設計一般都是 客戶端-線程模型,新來一個客戶端連接請求,就新建一個線程處理連接和數據傳輸
當客戶端連接較多時就會大大消耗服務器的資源,線程數量可能超過最大承受量
偽異步I/O
與BIO類似,只是將客戶端-線程的模式換成了線程池,可以靈活設置線程池的大小。但這只是對BIO的一種優化手段,并沒有解決線程連接的阻塞問題。
NIO
同步非阻塞I/O模型,利用selector多路復用器輪詢為每一個用戶創建連接,這樣就不用阻塞用戶線程,也不用每個線程忙等待。只使用一個線程輪詢I/O事件,比較適合高并發,高負載的網絡應用,充分利用系統資源快速處理請求返回響應消息,是和連接較多連接時間I/O任務較短
AIO
異步非阻塞,需要操作系統內核線程支持,一個用戶線程發起一個請求后就可以繼續執行,內核線程執行完系統調用后會根據回調函數完成處理工作。比較適合較多I/O任務較長的場景。
三、select
監視多個文件句柄的狀態變化,程序會阻塞在select處等待,直到有文件描述符就緒或超時。
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)可以監聽三類文件描述符,writefds(寫狀態), readfds(讀狀態), exceptfds(異常狀態)。
我們在select函數中告訴內核需要監聽的不同狀態的文件描述符以及能接受的超時時間,函數會返回所有狀態下就緒的描述符的個數,并且可以通過遍歷fdset,來找到就緒的描述符。
缺陷
- 每次調用select,都需要把待監控的fd集合從用戶態拷貝到內核態,當fd很大時,開銷很大。
- 每次調用select,都需要輪詢一遍所有的fd,查看就緒狀態。
- select支持的最大文件描述符數量有限,默認是1024
四、poll
與select輪詢所有待監聽的描述符機制類似,但poll使用pollfd結構表示要監聽的描述符。
int poll(struct pollfd *fds, nfds_t nfds, int timeout) struct pollfd{ short events; short revents;};pollfd結構包括了events(要監聽的事件)和revents(實際發生的事件)。而且也需要在函數返回后遍歷pollfd來獲取就緒的描述符。
相對于select,poll已不存在最大文件描述符限制。
五、epoll
epoll針對以上select和poll的主要缺點做出了改進,
主要包括三個主要函數,epoll_create, epoll_ctl, epoll_wait。
- epoll_create:創建epoll句柄,會占用一個fd值,使用完成以后,要關閉。
int epoll_create(int size)
- epoll_ctl:提前注冊好要監聽的事件類型,監聽事件(文件可寫,可讀,掛斷,錯誤)。不用每次都去輪詢一遍注冊的fd,而只是通過epoll_ctl把所有fd拷貝進內核一次,并為每一個fd指定一個回調函數。
當就緒,會調用回調函數,把就緒的文件描述符和事件加入一個就緒鏈表,并拷貝到用戶空間內存,應用程序不用親自從內核拷貝。類似于在信號中注冊所有的發送者和接收者,或者Task中注冊所有任務的handler。
- epoll_wait:監聽epoll_ctl中注冊的文件描述符和事件,在就緒鏈表中查看有沒有就緒的fd,不用去遍歷所有fd。
- 相當于直接去遍歷結果集合,而且百分百命中,不用每次都去重新查找所有的fd,用戶索引文件的事件復雜度為O(1)
六、select & poll & epoll比較
表面上看epoll的性能最好,但是在連接數少并且鏈接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機制需要很多函數回調。
select效率低是一位每次都需要輪詢,但效率低也是相對的,也可通過良好的設計改善
七、阻塞、非阻塞
這張圖可以看出阻塞式I/O、非阻塞式I/O、I/O復用、信號驅動式I/O他們的第二階段都相同,也就是都會阻塞到recvfrom調用上面就是圖中“發起”的動作。異步式I/O兩個階段都要處理。這里我們重點對比阻塞式I/O(也就是我們常說的傳統的BIO)和I/O復用之間的區別。
阻塞式I/O和I/O復用,兩個階段都阻塞,那區別在哪里呢?
雖然第一階段都是阻塞,但是阻塞式I/O如果要接收更多的連接,就必須創建更多的線程。I/O復用模式下在第一個階段大量的連接統統都可以過來直接注冊到Selector復用器上面,同時只要單個或者少量的線程來循環處理這些連接事件就可以了,一旦達到“就緒”的條件,就可以立即執行真正的I/O操作。這就是I/O復用與傳統的阻塞式I/O最大的不同。也正是I/O復用的精髓所在。
從應用進程的角度去理解始終是阻塞的,等待數據和將數據復制到用戶進程這兩個階段都是阻塞的。這一點我們從應用程序是可以清楚的得知,比如我們調用一個以I/O復用為基礎的NIO應用服務。調用端是一直阻塞等待返回結果的。
從內核的角度等待Selector上面的網絡事件就緒,是阻塞的,如果沒有任何一個網絡事件就緒則一直等待直到有一個或者多個網絡事件就緒。但是從內核的角度考慮,有一點是不阻塞的,就是復制數據,因為內核不用等待,當有就緒條件滿足的時候,它直接復制,其余時間在處理別的就緒的條件。這也是大家一直說的非阻塞I/O。實際上是就是指的這個地方的非阻塞。
總結
我們通常說的NIO大多數場景下都是基于I/O復用技術的NIO,比如jdk中的NIO,當然Tomcat8以后的NIO也是指的基于I/O復用的NIO。注意,使用NIO != 高性能,當連接數<1000,并發程度不高或者局域網環境下NIO并沒有顯著的性能優勢。如果放到線上環境,網絡情況在有時候并不穩定的情況下,這種基于I/O復用技術的NIO的優勢就是傳統BIO不可同比的了。那么使用select的優勢在于我們可以等到網絡事件就緒,那么用少量的線程去輪詢Selector上面注冊的事件,不就緒的不處理,就緒的拿出來立即執行真正的I/O操作。這樣我們就能夠用極少量的線程去HOLD住大量的連接。
后面會分享更多devops和DBA方面的內容,感興趣的朋友可以關注下~
總結
以上是生活随笔為你收集整理的两个线程同时从服务器接收消息_一文看懂I/O多路复用技术(mysql线程池)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jsp 中forward 和 Redir
- 下一篇: oracle decode_ORACLE