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