Linux的IO模型
阻塞I/O(Blocking I/O)
当应用程序发起一个I/O请求时,操作系统会进入内核等待数据准备好,数据读取完成后再返回到用户态。此时进程会因为等待I/O操作而长时间阻塞。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h>
#include <sys/time.h>
#include <errno.h>
void print_timestamp(const char* prefix) {
struct timeval tv;
gettimeofday(&tv, NULL);
char timestamp[64];
strftime(timestamp, sizeof(timestamp), "%H:%M:%S", localtime(&tv.tv_sec));
printf("%s: %s.%06ld\n", prefix, timestamp, tv.tv_usec);
}
int main() {
const char* fifo_path = "test_fifo";
// 创建FIFO(命名管道)
unlink(fifo_path); // 如果已存在则删除
if (mkfifo(fifo_path, 0666) == -1) {
perror("mkfifo failed");
return 1;
}
printf("FIFO created. Waiting for data...\n");
printf("To write to the FIFO, use: echo \"your message\" > test_fifo\n\n");
// 以阻塞模式打开FIFO
int fd = open(fifo_path, O_RDONLY);
if (fd == -1) {
perror("open failed");
unlink(fifo_path);
return 1;
}
char buffer[1024];
int read_count = 0;
while (read_count < 5) { // 读取5次后退出
print_timestamp("Waiting for data");
printf("Attempting read #%d... (blocked until data arrives)\n", read_count + 1);
// 阻塞读取
ssize_t bytes = read(fd, buffer, sizeof(buffer) - 1);
// 不能立马执行打印,只有当读取到数据之后再回继续
print_timestamp("Read completed");
if (bytes > 0) {
buffer[bytes] = '\0';
printf("Read %zd bytes: %s\n", bytes, buffer);
} else if (bytes == 0) {
printf("Writer closed the FIFO\n");
break;
} else {
perror("read failed");
break;
}
read_count++;
printf("\nWaiting for next input...\n\n");
}
close(fd);
unlink(fifo_path);
return 0;
}
非阻塞I/O(Non-blocking I/O)
非阻塞I/O模型使应用程序可以在I/O未就绪时立即返回,而不是被阻塞。应用程序在非阻塞模式下,会不断轮询(polling)内核检查I/O状态,直到数据准备好。这种方式避免了进程阻塞,但轮询消耗CPU资源,使其适合对实时响应性要求高的场景,但在大量I/O情况下效率不佳。而且应用程序无法得知数据什么时候就绪,只能做到插入一段逻辑之后继续轮询。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h>
#include <sys/time.h>
#include <errno.h>
void print_timestamp(const char* prefix) {
struct timeval tv;
gettimeofday(&tv, NULL);
char timestamp[64];
strftime(timestamp, sizeof(timestamp), "%H:%M:%S", localtime(&tv.tv_sec));
printf("%s: %s.%06ld\n", prefix, timestamp, tv.tv_usec);
}
void set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
perror("fcntl F_GETFL failed");
exit(1);
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
perror("fcntl F_SETFL failed");
exit(1);
}
}
int main() {
const char* fifo_path = "test_fifo";
// 创建FIFO
unlink(fifo_path);
if (mkfifo(fifo_path, 0666) == -1) {
perror("mkfifo failed");
return 1;
}
printf("FIFO created. Demo of non-blocking I/O...\n");
printf("To write to the FIFO, use: echo \"your message\" > test_fifo\n\n");
// 以非阻塞模式打开FIFO
int fd = open(fifo_path, O_RDONLY | O_NONBLOCK);
if (fd == -1) {
perror("open failed");
unlink(fifo_path);
return 1;
}
char buffer[1024];
int read_count = 0;
// 持续运行,直到读取到5次数据
while (read_count < 5) {
print_timestamp("Attempting read");
printf("Waiting for data... (read count: %d/5)\n", read_count);
// 非阻塞读取
ssize_t bytes = read(fd, buffer, sizeof(buffer) - 1);
if (bytes > 0) {
// 成功读取数据
buffer[bytes] = '\0';
print_timestamp("Data received");
printf("Read %zd bytes: %s\n", bytes, buffer);
read_count++;
printf("\nWaiting for next input...\n\n");
} else if (bytes == 0) {
// 写入端关闭
read_count++;
printf("Writer closed the FIFO\n");
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 没有数据可读
printf("No data available right now\n");
printf("Process can do other things here instead of blocking...\n");
} else {
// 其他错误
perror("read failed");
break;
}
printf("-----------------------------------\n");
sleep(2); // 等待2秒再次尝试
}
printf("Successfully read 5 messages. Exiting...\n");
close(fd);
unlink(fifo_path);
return 0;
}
I/O多路复用(I/O Multiplexing)
I/O多路复用使用select、poll或epoll等系统调用来监控多个文件描述符,一旦某个描述符的状态发生变化,就通知应用程序。这样就可以在单个线程或进程中管理多个I/O操作,常用于高并发场景。I/O多路复用避免了轮询消耗的CPU资源,是Web服务器等高并发应用的常用模型。
该模型仍然会阻塞线程来等待结果,任意一个文件描述符就绪都会解除阻塞。