非阻塞I/O的定义及实现机制:设备不可用时的处理逻辑解析
Great question—this is one of those nuanced details that's often skipped in high-level explanations, so it’s totally reasonable to want a clear breakdown. Let’s dig into exactly how non-blocking I/O works, and which of your two proposed models it uses.
核心结论:非阻塞I/O会立即返回“未就绪”错误,而非后台并发执行操作
First off, let’s cut to the chase: when an I/O device isn’t ready (e.g., no data to read, no buffer space to write), non-blocking I/O does not start the I/O operation in the background and return later. Instead, it immediately returns a specific error code telling your program, "I can’t do this right now—try again later."
具体实现与流程
Let’s walk through the step-by-step mechanics, using Linux as an example (since this pattern is consistent across most Unix-like systems, and similar concepts apply to Windows with slight API differences):
1. 先把I/O对象设置为非阻塞模式
Before you can use non-blocking I/O, you need to explicitly mark the file descriptor (socket, file, pipe, etc.) as non-blocking. For example, using the fcntl() system call:
int fd = open("some_file.txt", O_RDONLY); // Get current flags int flags = fcntl(fd, F_GETFL, 0); // Add non-blocking flag fcntl(fd, F_SETFL, flags | O_NONBLOCK);
This tells the kernel: "Don’t block any future I/O calls on this descriptor—if you can’t complete the operation right away, just tell me and move on."
2. 发起I/O调用,内核快速检查就绪状态
When you call an I/O function like read() or write() on the non-blocking descriptor:
- The kernel first checks if the I/O device is ready to perform the operation:
- For a read: Is there data available in the kernel’s receive buffer?
- For a write: Is there free space in the kernel’s send buffer?
- If the device is ready: The kernel completes the operation (copies data to/from user space) and returns the result (number of bytes read/written).
- If the device is not ready: The kernel does not block your process to wait for readiness. Instead, it immediately returns
-1and sets theerrnovariable toEAGAIN(orEWOULDBLOCK, which is usually the same value on most systems).
3. 你的程序处理返回结果
When your program gets that EAGAIN error, it knows the I/O operation couldn’t be completed immediately. At this point, you have options:
- Poll later: You can loop back to try the I/O call again after doing other work (though this is inefficient for most cases).
- Use I/O multiplexing: The smarter approach is to use tools like
epoll,select, orpollto monitor multiple descriptors. These functions block (or can be used in non-blocking mode) until one or more descriptors are ready for I/O. When they notify you a descriptor is ready, you can call the non-blocking I/O function again—and this time it will succeed immediately.
为什么你会误以为是“并发执行”?
The confusion with your second proposed model (background I/O + later return) comes from how non-blocking I/O is often used in practice. When combined with I/O multiplexing, your program can:
- Ask the kernel to watch for readiness on multiple descriptors.
- Do other work while waiting for the kernel to signal that a descriptor is ready.
- Handle the ready descriptor’s I/O when notified.
This makes it seem like the I/O is happening concurrently, but in reality, the I/O operation only runs when your program explicitly initiates it (after the kernel says it’s ready). The "concurrency" is just your program using its time efficiently instead of blocking on a single I/O call.
关键区别:非阻塞I/O vs 异步I/O
It’s important to note that non-blocking I/O is not the same as asynchronous I/O (AIO). AIO is the model where you initiate an I/O operation, the kernel runs it in the background, and notifies you when it’s complete (regardless of whether it succeeded or failed). Non-blocking I/O, by contrast, requires your program to actively check (or be notified of) readiness before initiating a successful I/O operation.
内容的提问来源于stack exchange,提问作者Shoblade X

