IO全称为输入/输出(Input/Output,正规简称I/O),指的是计算机系统与外部设备之间的数据交换。外部设备包括输入设备(如鼠标、键盘等)、输出设备(如显示器)、存储设备(如硬盘)和网络设备等。
传统的I/O操作是阻塞的,这意味着当一个I/O操作进行时,当前的执行体会被挂起,进入等待状态,直到I/O结果返回,执行体才会继续处理后续的逻辑。这就像你去图书馆前台借一本非常热门的书,但书已经被借出。为了得到这本书,你选择站在前台等待,直到这本书被归还。等待的过程中,你不能去干其它任何事情。
非阻塞I/O更像是这样:你去借那本热门书籍,但被告知现在没有。这时,你留下联系方式并告诉图书管理员,一旦书归还,请通知你。然后你可以自由地去参加其他活动。
在借书的这个例子中,你不用浪费大量的时间在等待上,同样的时间你可以做更多的事,可以说,非阻塞I/O极大的提高了系统运行效率。另外还有很多同学说非阻塞IO快,阻塞IO慢,真的是这样吗?
本文,我们将深入探讨阻塞I/O遇到的问题,非阻塞I/O的原理、优势及其实现方法,帮助大家更好地理解和应用这一技术。
阻塞IO为什么被诟病?
在高并发场景下,如果使用阻塞I/O模型,每个请求都需要创建一个新的线程来处理。当这些请求中有大量操作处于I/O等待状态时,虽然CPU能够切换到其他任务继续执行,但创建和管理大量线程本身也会消耗系统资源,包括内存和用于线程上下文切换的CPU时间,从而影响系统的整体性能和可扩展性。
正如上面借书的例子,当IO操作发生时,我们无需等待,可以去干别的事,只有IO操作返回时,我们才需要处理IO返回的结果,这就是非阻塞IO的本质。
非阻塞IO可以解决阻塞IO的内存占用过大和上下文切换频繁问题,下边我将介绍几个典型的非阻塞IO模型,方便大家理解其中的原理。
Java NIO(New I/O)引入了非阻塞I/O机制,通过Channel和Buffer来处理数据,使用Selector来管理多个Channel。
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()); } } } }}
asyncio是Python标准库中的一个库,提供了异步I/O支持。它基于事件循环(event loop),可以调度和执行异步任务(coroutines)。
import asyncioasync 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使用事件驱动模型和非阻塞I/O操作,基于libuv库实现。libuv是一个跨平台的异步I/O库,封装了不同操作系统的I/O多路复用机制(如epoll、kqueue、IOCP等)。
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和channel提供了轻量级的并发支持。goroutine是Go语言中的轻量级线程,或者也叫协程,通过channel进行通信和同步。select语句用于监听多个channel的操作,实现非阻塞I/O。
package mainimport ( "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) }}
对于单次IO,从发起到收到响应,其中主要有三段时间:请求数据从客户端到服务端的传输时间、服务端的处理时间、响应数据从服务端到客户端的返回时间。对于这三段时间,非阻塞IO和阻塞IO都没有任何影响力或者说影响甚小,它们都不会因为使用非阻塞IO而变的更快。
但是非阻塞IO因为更优的内存使用效率,服务器可以支撑更大的并发访问,在繁忙的系统中,如果存在因为内存分配或者线程调度而导致请求接入等待的情况,非阻塞IO一定程度上会降低请求接入的平均时间,从而让服务端的处理更快一些。不过这是非阻塞IO结合协程机制的效果,单纯非阻塞IO没有这个能力。
以上就是本文的主要内容。非阻塞I/O通过更高效的资源利用和更低的线程管理开销,显著提升了系统在高并发场景下的性能和扩展性。尽管它不能直接加快单次I/O操作的速度,但其在整体性能优化方面的优势使其成为现代软件系统中不可或缺的重要部分。掌握非阻塞I/O技术,对于开发高性能、高可扩展性的应用至关重要。希望本文能帮助大家更好地理解和应用这一技术。
本文链接://www.dmpip.com//www.dmpip.com/showinfo-26-94847-0.html为什么高手都要用非阻塞IO?
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com