自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

為什么高手都要用非阻塞IO?

開發(fā) 前端
對于單次IO,從發(fā)起到收到響應,其中主要有三段時間:請求數(shù)據(jù)從客戶端到服務端的傳輸時間、服務端的處理時間、響應數(shù)據(jù)從服務端到客戶端的返回時間。

IO全稱為輸入/輸出(Input/Output,正規(guī)簡稱I/O),指的是計算機系統(tǒng)與外部設備之間的數(shù)據(jù)交換。外部設備包括輸入設備(如鼠標、鍵盤等)、輸出設備(如顯示器)、存儲設備(如硬盤)和網(wǎng)絡設備等。

傳統(tǒng)的I/O操作是阻塞的,這意味著當一個I/O操作進行時,當前的執(zhí)行體會被掛起,進入等待狀態(tài),直到I/O結(jié)果返回,執(zhí)行體才會繼續(xù)處理后續(xù)的邏輯。這就像你去圖書館前臺借一本非常熱門的書,但書已經(jīng)被借出。為了得到這本書,你選擇站在前臺等待,直到這本書被歸還。等待的過程中,你不能去干其它任何事情。

非阻塞I/O更像是這樣:你去借那本熱門書籍,但被告知現(xiàn)在沒有。這時,你留下聯(lián)系方式并告訴圖書管理員,一旦書歸還,請通知你。然后你可以自由地去參加其他活動。

在借書的這個例子中,你不用浪費大量的時間在等待上,同樣的時間你可以做更多的事,可以說,非阻塞I/O極大的提高了系統(tǒng)運行效率。另外還有很多同學說非阻塞IO快,阻塞IO慢,真的是這樣嗎?

本文,我們將深入探討阻塞I/O遇到的問題,非阻塞I/O的原理、優(yōu)勢及其實現(xiàn)方法,幫助大家更好地理解和應用這一技術(shù)。

阻塞IO的真正問題

阻塞IO為什么被詬???

在高并發(fā)場景下,如果使用阻塞I/O模型,每個請求都需要創(chuàng)建一個新的線程來處理。當這些請求中有大量操作處于I/O等待狀態(tài)時,雖然CPU能夠切換到其他任務繼續(xù)執(zhí)行,但創(chuàng)建和管理大量線程本身也會消耗系統(tǒng)資源,包括內(nèi)存和用于線程上下文切換的CPU時間,從而影響系統(tǒng)的整體性能和可擴展性。

  • 內(nèi)存占用:在Windows系統(tǒng)中默認每個線程分配1M的內(nèi)存,在Linux系統(tǒng)中默認分配8M,假設我們的計算機有8G的內(nèi)存,那么系統(tǒng)最多也只能創(chuàng)建幾千個線程,這個數(shù)量級顯然無法滿足高并發(fā)場景下處理數(shù)十萬甚至上百萬并發(fā)連接的需求。具體來說,如果按照Linux系統(tǒng)默認每個線程分配8M內(nèi)存來計算,8G內(nèi)存理論上最多能支持約1000個線程(8GB / 8MB = 1024),但實際上,系統(tǒng)還需要保留內(nèi)存給其他進程使用,因此可用線程數(shù)會更少。
  • 上下文切換:當操作系統(tǒng)從一個線程切換到另一個線程時,需要保存當前線程的狀態(tài)(如寄存器內(nèi)容)并加載新線程的狀態(tài),這個過程涉及多次內(nèi)存讀寫操作,會占用CPU周期且可能導致延遲。在高并發(fā)場景下,阻塞IO會導致大量的線程產(chǎn)生,從而導致頻繁的線程切換,而頻繁的線程上下文切換會顯著降低系統(tǒng)效率。

非阻塞IO的基本原理

什么是非阻塞IO?

正如上面借書的例子,當IO操作發(fā)生時,我們無需等待,可以去干別的事,只有IO操作返回時,我們才需要處理IO返回的結(jié)果,這就是非阻塞IO的本質(zhì)。

非阻塞IO可以解決阻塞IO的內(nèi)存占用過大和上下文切換頻繁問題,下邊我將介紹幾個典型的非阻塞IO模型,方便大家理解其中的原理。

Java NIO

Java NIO(New I/O)引入了非阻塞I/O機制,通過Channel和Buffer來處理數(shù)據(jù),使用Selector來管理多個Channel。

  • Channel:Channel是數(shù)據(jù)傳輸?shù)耐ǖ?,可以進行非阻塞的讀寫操作。
  • Buffer:Buffer是數(shù)據(jù)的容器,用于讀寫數(shù)據(jù)。Buffer直接管理一塊操作系統(tǒng)內(nèi)存,減少了數(shù)據(jù)拷貝,支持多種數(shù)據(jù)類型,讀寫更方便、效率更高。
  • Selector:Selector是多路復用器,允許一個線程同時監(jiān)控多個Channel的狀態(tài)(如讀、寫、連接等),從而實現(xiàn)非阻塞I/O。
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;
import java.util.Iterator;

public class NIOServer {
    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.bind(new InetSocketAddress("localhost", 8080));
        serverSocket.configureBlocking(false);
        serverSocket.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            selector.select();
            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
            while (keys.hasNext()) {
                SelectionKey key = keys.next();
                keys.remove();
                if (key.isAcceptable()) {
                    SocketChannel client = serverSocket.accept();
                    client.configureBlocking(false);
                    client.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(256);
                    client.read(buffer);
                    System.out.println("Received: " + new String(buffer.array()).trim());
                }
            }
        }
    }
}

Python asyncio

asyncio是Python標準庫中的一個庫,提供了異步I/O支持。它基于事件循環(huán)(event loop),可以調(diào)度和執(zhí)行異步任務(coroutines)。

  • 事件循環(huán):事件循環(huán)是asyncio的核心,負責調(diào)度和執(zhí)行異步任務。事件循環(huán)有點類似Java NIO中的Select,不過事件循環(huán)是更高級的抽象,除了通過操作系統(tǒng)的多路復用機制調(diào)度IO,它還調(diào)度協(xié)程、定時器和信號處理器。
  • 協(xié)程:協(xié)程是比線程更小的程序執(zhí)行單位,更小的內(nèi)存分配,更短的切換時間,是編程語言在用戶態(tài)維護管理的。協(xié)程是使用async/await關鍵字定義的函數(shù),可以在等待I/O操作時掛起并讓出控制權(quán),從而實現(xiàn)并發(fā)。
import asyncio

async def handle_client(reader, writer):
    data = await reader.read(100)
    message = data.decode()
    print(f"Received: {message}")

    writer.write(data)
    await writer.drain()
    writer.close()

async def main():
    server = await asyncio.start_server(handle_client, '127.0.0.1', 8888)
    async with server:
        await server.serve_forever()

asyncio.run(main())

Node.js的事件驅(qū)動模型

圖片圖片

Node.js使用事件驅(qū)動模型和非阻塞I/O操作,基于libuv庫實現(xiàn)。libuv是一個跨平臺的異步I/O庫,封裝了不同操作系統(tǒng)的I/O多路復用機制(如epoll、kqueue、IOCP等)。

  • 事件循環(huán):與Python asyncio的事件循環(huán)類似,不過Node.js底層基于libuv,側(cè)重的是IO操作,通過檢查事件隊列,調(diào)用相應的回調(diào)函數(shù)來處理事件。Node.js 的事件循環(huán)主要針對構(gòu)建高并發(fā)的網(wǎng)絡應用,特別是I/O密集型任務,如Web服務器、API服務器等。它利用了非阻塞I/O和事件驅(qū)動模型,允許在單線程中處理大量并發(fā)連接。
  • 回調(diào)函數(shù):回調(diào)函數(shù)是處理異步操作的主要方式,當I/O操作完成時,調(diào)用相應的回調(diào)函數(shù)。
  • Promise:Promise是一種異步編程的模式,用于處理異步操作的結(jié)果。
  • async/await:async/await是基于Promise的語法糖,使得異步代碼看起來更像同步代碼。
const http = require('http');

const server = http.createServer((req, res) => {
    if (req.method === 'GET') {
        res.writeHead(200, { 'Content-Type': 'text/plain' });
        res.end('Hello, World!\n');
    }
});

server.listen(8080, '127.0.0.1', () => {
    console.log('Server running at http://127.0.0.1:8080/');
});

Go語言的goroutine

圖片圖片

Go語言通過goroutine和channel提供了輕量級的并發(fā)支持。goroutine是Go語言中的輕量級線程,或者也叫協(xié)程,通過channel進行通信和同步。select語句用于監(jiān)聽多個channel的操作,實現(xiàn)非阻塞I/O。

  • goroutine:goroutine是Go語言中的協(xié)程,可以并發(fā)執(zhí)行函數(shù)。協(xié)程的內(nèi)存占用更小,使用的操作系統(tǒng)線程更少。
  • channel:channel用于在goroutine之間傳遞消息,實現(xiàn)同步和通信。
  • select:select語句用于監(jiān)聽多個channel的操作,可以實現(xiàn)非阻塞I/O。
package main

import (
    "fmt"
    "net"
    "bufio"
)

func handleConnection(conn net.Conn) {
    defer conn.Close()
    reader := bufio.NewReader(conn)
    for {
        message, _ := reader.ReadString('\n')
        fmt.Printf("Received: %s", message)
        conn.Write([]byte(message))
    }
}

func main() {
    listener, _ := net.Listen("tcp", "localhost:8080")
    defer listener.Close()
    for {
        conn, _ := listener.Accept()
        go handleConnection(conn)
    }
}

非阻塞I/O的設計共性

  • 多路復用:非阻塞IO都直接或間接使用了多路復用,這是一種高效的I/O處理技術(shù),允許一個線程同時監(jiān)控多個I/O通道。常見的多路復用機制有epoll(Linux)、kqueue(BSD)、IOCP(Windows)等。
  • 協(xié)程:盡管一些語言沒有明確的提出這一概念,但都蘊含了協(xié)程的思想。協(xié)程是一種輕量級的并發(fā)處理機制,可以暫停和恢復執(zhí)行,內(nèi)存占用更小,切換成本更低,運行在用戶態(tài),比傳統(tǒng)線程更高效。協(xié)程通過非阻塞I/O操作和事件循環(huán)實現(xiàn)并發(fā)處理。
  • 事件驅(qū)動:事件驅(qū)動是一種編程模式,通過事件通知機制來處理I/O操作的完成。程序不主動等待I/O操作,而是注冊一個事件,當I/O操作完成時,事件觸發(fā)相應的處理程序。觸發(fā)形式可能是簡單的回調(diào),也可能是復雜的執(zhí)行體(線程、協(xié)程等)調(diào)度。

非阻塞IO更快嗎?

對于單次IO,從發(fā)起到收到響應,其中主要有三段時間:請求數(shù)據(jù)從客戶端到服務端的傳輸時間、服務端的處理時間、響應數(shù)據(jù)從服務端到客戶端的返回時間。對于這三段時間,非阻塞IO和阻塞IO都沒有任何影響力或者說影響甚小,它們都不會因為使用非阻塞IO而變的更快。

但是非阻塞IO因為更優(yōu)的內(nèi)存使用效率,服務器可以支撐更大的并發(fā)訪問,在繁忙的系統(tǒng)中,如果存在因為內(nèi)存分配或者線程調(diào)度而導致請求接入等待的情況,非阻塞IO一定程度上會降低請求接入的平均時間,從而讓服務端的處理更快一些。不過這是非阻塞IO結(jié)合協(xié)程機制的效果,單純非阻塞IO沒有這個能力。

以上就是本文的主要內(nèi)容。非阻塞I/O通過更高效的資源利用和更低的線程管理開銷,顯著提升了系統(tǒng)在高并發(fā)場景下的性能和擴展性。盡管它不能直接加快單次I/O操作的速度,但其在整體性能優(yōu)化方面的優(yōu)勢使其成為現(xiàn)代軟件系統(tǒng)中不可或缺的重要部分。掌握非阻塞I/O技術(shù),對于開發(fā)高性能、高可擴展性的應用至關重要。希望本文能幫助大家更好地理解和應用這一技術(shù)。

責任編輯:武曉燕 來源: 螢火架構(gòu)
相關推薦

2023-12-06 07:28:47

阻塞IO異步IO

2025-01-17 00:00:00

APIJava線程

2023-12-13 09:45:49

模型程序

2009-01-09 23:06:41

服務器SCSI硬盤PC

2020-04-07 16:12:56

Go編程語言開發(fā)

2021-05-11 06:57:15

HBaseBATJ公司

2024-07-02 13:27:38

2021-12-13 01:40:29

ElasticSear倒排索引

2024-01-02 17:28:12

芯片CPUAI計算

2022-05-07 07:35:44

工具讀寫鎖Java

2021-06-04 18:14:15

阻塞非阻塞tcp

2016-11-08 11:06:20

2023-07-31 08:55:01

Java NIO非阻塞阻塞

2015-07-01 10:25:07

Docker開源項目容器

2023-09-22 10:05:32

2016-01-12 16:58:31

C游戲

2022-07-06 09:29:40

JMH性能測試

2011-03-15 14:54:08

NoSQL

2021-10-13 06:49:15

網(wǎng)絡 IO

2021-02-09 20:51:13

D 語言腳本編程語言
點贊
收藏

51CTO技術(shù)棧公眾號