
I/O truyền thống có thể chặn chương trình, gây trễ. Non-blocking I/O cho phép chương trình tiếp tục chạy trong khi I/O đang diễn ra. Hàm select() trong C thường dùng để thực hiện non-blocking I/O, giám sát nhiều file descriptor (socket, file handle) xem sẵn sàng đọc/ghi hay lỗi. select() giúp quản lý nhiều tác vụ I/O hiệu quả mà không chặn thực thi.
Hàm select() là gì?
select() là công cụ mạnh cho non-blocking I/O. Nó giám sát ba tập file descriptor: read set, write set, exception set. Ta chỉ định descriptor nào cần theo dõi cho thao tác nào. Hàm nhận giá trị timeout (thời gian chờ tối đa). Khi có sự kiện trên descriptor nào đó hoặc hết timeout, select() trả về và cho biết descriptor nào sẵn sàng. Điều này cho phép thực hiện I/O hiệu quả, phù hợp xử lý nhiều I/O đồng thời.
Ưu điểm: xử lý nhiều I/O không cần thread mỗi kết nối, giảm tiêu thụ tài nguyên. Nhược điểm: giới hạn số descriptor tối đa (thường do OS quy định), hiệu năng giảm khi số descriptor lớn.
Ví dụ lập trình
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
int fd1, fd2;
// Mở file ở chế độ non-blocking
fd1 = open("file1.txt", O_RDONLY | O_NONBLOCK);
fd2 = open("file2.txt", O_WRONLY | O_NONBLOCK);
fd_set read_fds, write_fds;
struct timeval timeout;
while (1) {
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
FD_SET(fd1, &read_fds);
FD_SET(fd2, &write_fds);
timeout.tv_sec = 4;
timeout.tv_usec = 0;
int ready_fds = select(fd2 + 1, &read_fds, &write_fds, NULL, &timeout);
if (ready_fds == -1) {
perror("select");
exit(EXIT_FAILURE);
} else if (ready_fds == 0) {
printf("Timeout occurred\n");
} else {
if (FD_ISSET(fd1, &read_fds)) {
// fd1 sẵn sàng đọc
char buffer[100];
ssize_t bytesRead = read(fd1, buffer, sizeof(buffer) - 1);
if (bytesRead > 0) {
buffer[bytesRead] = '\0';
printf("Read from file1.txt: %s \n", buffer);
}
}
if (FD_ISSET(fd2, &write_fds)) {
// fd2 sẵn sàng ghi
const char* message = "Good morning";
ssize_t bytesWritten = write(fd2, message, strlen(message));
if (bytesWritten > 0) {
printf("Wrote to file2.txt: %s \n", message);
}
}
}
}
close(fd1);
close(fd2);
return 0;
}
Kết quả:
Wrote to file2.txt: Good morning
Wrote to file2.txt: Good morning
Wrote to file2.txt: Good morning
Wrote to file2.txt: Good morning
Timeout occurred
Giải thích
Chương trình giám sát file1.txt (đọc) và file2.txt (ghi) ở chế độ non-blocking. select() kiểm tra hoạt động trong 4 giây. Nếu không có hoạt động, in "Timeout occurred". Nếu có, kiểm tra descriptor nào sẵn sàng: đọc từ file1.txt và in nội dung, hoặc ghi "Good morning" vào file2.txt. Vòng lặp chạy vô hạn cho đến khi kết thúc, sau đó đóng descriptor.
Kết luận
select() cung cấp giải pháp tốt cho non-blocking I/O trong C, cho phép giám sát nhiều file descriptor, xử lý nhiều tác vụ I/O không chặn. Tuy nhiên cần lưu ý giới hạn số descriptor và hiệu năng khi số lượng lớn. Vẫn là lựa chọn phổ biến quản lý non-blocking I/O.
Tác giả

Bamdeb Ghosh
Chuyên gia mạng không dây, phân tích Wireshark, am hiểu Android, Bluetooth, Linux, Python. Trang web: wifisharks.com