0

getline và select, một sự phối hợp

select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &timeout)

Cách dễ hiểu nhất là xem select giải quyết vấn đề gì trước, rồi mới đến từng tham số.

1. Vấn đề: getline bắt chương trình đứng chờ

std::getline(std::cin, line);

Khi chạy tới dòng này mà người dùng chưa gõ gì, chương trình dừng hẳn ở đó cho đến khi người dùng nhấn Enter. Hiện tượng này gọi là blocking (bị chặn).

Nếu chương trình cần vừa làm việc khác (đếm giờ, vẽ game, nhận dữ liệu mạng...), vừa thỉnh thoảng nhận lệnh từ bàn phím, thì blocking là vấn đề.

2. Ý tưởng của select: hỏi trước, đọc sau

Hình dung fd 0 là hộp thư:

getline = mở hộp thư lấy thư. Nếu hộp trống, bạn đứng đó chờ đến khi có thư.

select = nhìn qua cửa kính xem có thư không. Có thì báo “có”, không thì báo “không”, rồi bạn đi làm việc khác.

3. fd_set: một tờ danh sách đánh dấu

fd_set giống một tờ giấy có 1024 ô, mỗi ô ứng với một số fd:

ô số:   0   1   2   3   4   ...  1023
        [ ] [ ] [ ] [ ] [ ]  ...  [ ]

FD_ZERO(&fds): xóa hết dấu tick

FD_SET(STDIN_FILENO, &fds): tick vào ô số 0

ô số:   0   1   2   3   4  ...  1023
        [✓] [ ] [ ] [ ] [ ]  ...  [ ]

Ý nghĩa: “Kernel ơi, tôi muốn hỏi về fd số 0.”

4. Từng tham số của lời gọi

select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &timeout)

Tham số 1: STDIN_FILENO + 1 (bằng 1)

Cho kernel biết cần xem bao nhiêu ô đầu tiên: từ ô 0 đến ô n - 1. Ta chỉ quan tâm ô 0, nên n = 0 + 1 = 1. Nếu theo dõi fd 0 và fd 5 thì phải truyền 5 + 1 = 6. Tham số này giúp kernel khỏi phải quét cả 1024 ô.

Tham số 2, 3, 4:

Thứ 2 Fd nào đọc được mà không phải chờ? -> &fds (hỏi về fd 0)

Thứ 3 Fd nào ghi được mà không phải chờ? -> nullptr (không hỏi)

Thứ 4 Fd nào có sự kiện đặc biệt? -> nullptr (không hỏi)

Ta chỉ cần biết bàn phím có dữ liệu chưa, nên chỉ điền tới tham số thứ 2

Tham số 5: &timeout, chờ bao lâu

nullptr Chờ mãi đến khi có fd sẵn sàng (giống getline, mất tác dụng)

{0, 0} Kiểm tra một lần, trả lời ngay lập tức

{1, 500000} Chờ tối đa 1,5 giây; nếu dữ liệu đến sớm thì trả về sớm

5. select trả về gì

select sửa lại tờ danh sách fds: ô nào chưa sẵn sàng sẽ bị xóa dấu tick, ô nào sẵn sàng thì giữ nguyên. Nó cũng trả về một con số:

1 Có 1 fd sẵn sàng: bàn phím có dữ liệu ô 0 vẫn tick

0 Hết thời gian chờ, chưa có gì ô 0 bị xóa tick

-1 Có lỗi không xác định

Vì chỉ theo dõi đúng một fd, so sánh == 1 là đủ biết bàn phím có dữ liệu hay không. Khi theo dõi nhiều fd, người ta dùng thêm FD_ISSET(fd, &fds) để hỏi từng ô xem cái nào còn tick.

6. Ví dụ chạy thử

Chương trình dưới đây cứ mỗi giây in ra một số đếm, không bị dừng lại chờ bạn gõ. Khi bạn gõ gì đó và nhấn Enter, nó đọc dòng đó; gõ quit để thoát.

#include <iostream>
#include <string>
#include <sys/select.h>
#include <unistd.h>

bool is_input_available() {
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(STDIN_FILENO, &fds);
    timeval timeout = {0, 0};
    return select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &timeout) == 1;
}

int main() {
    int count = 0;
    while (true) {
        if (is_input_available()) {
            std::string line;
            if (!std::getline(std::cin, line)) break; // gặp EOF thì thoát
            if (line == "quit") break;
            std::cout << ">>> Bạn gõ: " << line << "\n";
        }
        std::cout << "Đang làm việc... " << count++ << "\n";
        sleep(1);
    }
}

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí