硬體準備: CH32V103 開發板/核心版, WCH-Link. 軟體準備: 軟體主要是用於編譯的 RISC-V GCC , 和用於燒錄的 OpenOCD., RISC-V GCC 可以選擇公版或者WCH版, OpenOCD 暫時只能用WCH定製版本, 用公版的無法識別 wlink ...
-
BIO模型
在Linux中,預設情況下所有socket都是阻塞模式。用戶線程調用系統函數read()【system call】,內核開始準備數據(從磁碟/網路獲取數據),內核准備數據完成後,用戶線程完成數據從內核拷貝到用戶空間的應用程式緩衝區,數據拷貝完成後,請求才返回。從發起read請求到完成內核到應用程式的拷貝,整個過程都是阻塞的。
為了減輕線程阻塞的弊端,實際上,每個Read/Write請求都會分配單獨線程進行單獨處理。在低併發時期,這種每個請求每線程的處理方式是可以應付的,但是如果在高併發期間(如:業務高峰期),就會分配大量的線程完成請求處理。因此會帶來非常大的性能損耗。 -
NIO模型
用戶線程在發起Read請求後立即返回,不用等待內核准備數據的過程。如果Read請求沒讀取到數據,用戶線程會不斷輪詢發起Read請求,直到數據到達(內核准備好數據)後才停止輪詢。
非阻塞IO模型雖然避免了由於線程阻塞問題帶來的大量線程消耗,但是頻繁的重覆輪詢大大增加了請求次數,對CPU消耗也比較明顯。 -
多路復用模型
多路復用IO模型,建立在多路事件分離函數select,poll,epoll之上。在發起read請求前,先更新select的socket監控列表,然後等待select(或poll或epoll)函數返回(此過程是阻塞的)。當某個socket有數據到達時,select函數返回。此時用戶線程才正式發起read或write請求,處理數據。這種模式用一個專門的監視線程去檢查多個socket,如果某個socket有數據到達就交給工作線程處理。由於等待Socket數據到達過程非常耗時,所以這種方式解決了阻塞IO模型一個Socket連接就需要一個線程的問題,也不存在非阻塞IO模型忙輪詢帶來的CPU性能損耗的問題。
多路復用的本質,在我看來其實就是通過儘可能少(預期一次)的系統調用(system call),就可以拿到所有socket的狀態(是否可讀),然後程式只需要對那些返回狀態為可讀或可寫的socket進行處理。
1 // NIO核心代碼 2 // 初始化 3 channel = ServerSocketChannel.open(); 4 channel.bind(port); 5 channel.configureBlocking(false); 6 // selector 註冊accept事件 7 selector = Selector.open(); 8 channel.register(selector, SelectionKey.OP_ACCEPT); 9 while(true){ 10 while(selector.select(timeout)>0){ // 有新的事件 11 // 獲取到可處理的 Socket 12 Set<SelectionKey> keySet = selector.selectedKeys(); 13 Iterator<SelectionKey> iter = keySet.iterator(); 14 while (iter.hasNext()) { // 迴圈處理,處理之後應該從迭代器中移除 15 SelectionKey key = iter.next(); 16 iter.remove(); 17 if (key.isAcceptable()) { // 新連接 acceptHandle(key); } 18 else if (key.isReadable()) { // 讀事件 readHandle(key); } 19 else if (key.isWritable()) { // 寫事件 writeHandle(key); } 20 } 21 } 22 }
View Code實際上,linux的多路復用有三種實現方式,select和poll以及epoll,它們之間的關係是進化關係,性能都是遞進的。
-
select
select 是操作系統提供的系統調用函數,通過它,我們可以把一個文件描述符的數組【最大為1024個】發給操作系統內核, 讓內核去遍歷,確定哪個文件描述符可以讀寫,實際上只是打了一個標誌,哪些可讀可寫,所以在用戶程式獲取select的返回值的時候,仍然需要遍歷文件描述符的數組具有哪些可處理的事件。
select模型的優化在於將所有的文件描述符【其實就是正在監聽的socket連接】批量的傳給了內核,降低了系統調用,減少了內核態與用戶態的切換。而select的弊端就在於每次最多只能傳輸1024個文件描述符,這在一些高併發場景【redis緩存】下,仍然是不夠看的。此外select 在內核層仍然是通過遍歷的方式檢查文件描述符的就緒狀態,是個同步過程,只不過無系統調用切換上下文的開銷。 -
poll
poll對於select來講,最大的區別在於只是將每次只能傳輸1024個文件描述符的限制去掉了
-
epoll
epoll是在select和poll的基礎上做出的演進。他主要針對以下3點做出了改進
1.內核中保存一份文件描述符的集合,無需用戶程式每次懂重新傳遞【不需要拷貝】;
2.內核不再通過輪訓的方式獲取事件就緒的文件描述符,而是通過事件回調的方式將就緒事件放入到一個就緒隊列中【內核程式不需要進行O(N)的遍歷】;
3.內核僅會將就緒隊列中的文件描述符返回給用戶程式,用戶程式直接處理該描述符對應的事件即可【用戶程式不需要再次進行O(N)的遍歷,內核將不必要的文件描述符過濾了,因此發生的記憶體拷貝更少了】。 -
select、poll、epoll對比
AIO
不論是阻塞IO亦或是NIO,它們都是同步IO,即當內核將數據準備好的時候,都是由用戶線程將數據拷貝到用戶空間。除此之外還有一個非同步IO,即非阻塞IO,當數據準備好的時候,不需要用戶線程將數據拷貝到程式的運行空間,而是直接由內核線程完成數據的拷貝。
參考文件:
1.徹底搞懂IO多路復用
2.忘了一些重要的文章~~~
-