原子学习笔记1——阻塞和非阻塞IO

阻塞式 I/O 顾名思义就是对文件的 I/O 操作(读写操作)是阻塞式的,非阻塞式 I/O 同理就是对文件的I/O 操作是非阻塞的。
当对文件进行读操作时,如果数据未准备好、文件当前无数据可读,那么读操作可能会使调用者阻塞,直到有数据可读时才会被唤醒,这就是阻塞式 I/O 常见的一种表现;如果是非阻塞式 I/O,即使没有数据可读,也不会被阻塞、而是会立马返回错误。
普通文件的读写操作是不会阻塞的,不管读写多少个字节数据,read()或 write()一定会在有限的时间内返回,所以普通文件一定是以非阻塞的方式进行 I/O 操作,这是普通文件本质上决定的;但是对于某些文件类型,譬如管道文件、设备文件等,它们既可以使用阻塞式 I/O 操作,也可以使用非阻塞式 I/O进行操作。

1、阻塞IO读文件

在调用 open()函数打开文件时,为参数 flags 指定 O_NONBLOCK 标志,open()调用成功后,后续的 I/O 操作将以非阻塞式方式进行;这就是非阻塞 I/O 的打开方式,如果未指定 O_NONBLOCK 标志,则默认使用阻塞式 I/O 进行操作。
以读取鼠标为例,鼠标是一种输入设备,其对应的设备文件在/dev/input/目录下,如下所示:
原子学习笔记1——阻塞和非阻塞IO_第1张图片
使用 od 命令查看是哪个设备文件:

sudo od -x /dev/input/event3

移动鼠标或滚轮会打印出信息,如果没有试下其他的设备文件
原子学习笔记1——阻塞和非阻塞IO_第2张图片
下面以阻塞方式读取鼠标,调用 open()函数打开鼠标设备文件"/dev/input/event3",以只读方式打开,没有指定 O_NONBLOCK 标志,说明使用的是阻塞式 I/O;程序中只调用了一次 read()读取鼠标。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(void)
{
    char buf[100];
    int fd, ret;
    // /* 打开文件 
    fd = open("/dev/input/event3", O_RDONLY);
    if (-1 == fd) {
        perror("open error");
        exit(-1);
    }
    // /* 读文件 
    memset(buf, 0, sizeof(buf));
    ret = read(fd, buf, sizeof(buf));
    if (0 > ret) {
        perror("read error");
        close(fd);
        exit(-1);
    }
    printf("成功读取<%d>个字节数据\n", ret);
    // /* 关闭文件 
    close(fd);
    exit(0);
}

可以看出在不动滚轮的情况下阻塞
在这里插入图片描述
当移动滚轮是输出
原子学习笔记1——阻塞和非阻塞IO_第3张图片

2、非阻塞IO读文件

#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(void)
{
    char buf[100];
    int fd, ret;
    // /* 打开文件 
    fd = open("/dev/input/event3", O_RDONLY | O_NONBLOCK);
    if (-1 == fd) {
        perror("open error");
        exit(-1);
    }
    // /* 读文件 
    memset(buf, 0, sizeof(buf));
    ret = read(fd, buf, sizeof(buf));
    if (0 > ret) {
        perror("read error");
        close(fd);
        exit(-1);
    }
    printf("成功读取<%d>个字节数据\n", ret);
    // /* 关闭文件 
    close(fd);
    exit(0);
}

可以看到非阻塞状态返回错误,因为在执行程序时并没有滑动滚轮,程序就退出了
在这里插入图片描述
可以对上述代码修改,使用轮训方式不断地去读取,直到鼠标有数据可读,read()将会成功
返回:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(void)
{
    char buf[100];
    int fd, ret;
    // /* 打开文件 
    fd = open("/dev/input/event3", O_RDONLY | O_NONBLOCK);
    if (-1 == fd) {
        perror("open error");
        exit(-1);
    }
    // /* 读文件 
    memset(buf, 0, sizeof(buf));
    for ( ; ; ) {
        ret = read(fd, buf, sizeof(buf));
        if (0 < ret) {
            printf("成功读取<%d>个字节数据\n", ret);
            close(fd);
            exit(0);
        }
    }
}

用top命令可以看出用轮训方式此进程CPU占用率非常高,将近100
在这里插入图片描述
阻塞式 I/O 的优点在于能够提升 CPU 的处理效率,当自身条件不满足时,进入阻塞状态,交出 CPU资源,将 CPU 资源让给别人使用;而非阻塞式则是抓紧利用 CPU 资源,譬如不断地去轮训,这样就会导致该程序占用了非常高的 CPU 使用率!

3、使用阻塞 I/O 实现并发读取

使用阻塞式方式同时读取鼠标和键盘,示例代码如下所示:
键盘是标准输入设备 stdin,进程会自动从父进程中继承标准输入、标准输出以及标准错误,标准输入设备对应的文件描述符为 0,所以在程序当中直接使用即可,不需要再调用 open 打开。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define MOUSE "/dev/input/event3"
int main(void)
{
    char buf[100];
    int fd, ret;
    // /* 打开鼠标设备文件 
    fd = open(MOUSE, O_RDONLY);
    if (-1 == fd) {
        perror("open error");
        exit(-1);
    }
    // /* 读鼠标 
    memset(buf, 0, sizeof(buf));
    ret = read(fd, buf, sizeof(buf));
    printf("鼠标: 成功读取<%d>个字节数据\n", ret);
    // /* 读键盘 
    memset(buf, 0, sizeof(buf));
    ret = read(0, buf, sizeof(buf));
    printf("键盘: 成功读取<%d>个字节数据\n", ret);
    // /* 关闭文件 
    close(fd);
    exit(0);
}

原子学习笔记1——阻塞和非阻塞IO_第4张图片
可以看出阻塞IO无法实现同时读取,要先读鼠标再读键盘。用非阻塞就可以解决这个问题

4、使用非阻塞 I/O 实现并发读取

#include 
#include 
#include 
#include 
#include 
#include 
#define MOUSE "/dev/input/event3"
int main(void)
{
    char buf[100];
    int fd, ret, flag;
    // /* 打开鼠标设备文件 
    fd = open(MOUSE, O_RDONLY | O_NONBLOCK);
    if (-1 == fd) {
        perror("open error");
        exit(-1);
    }
    // /* 将键盘设置为非阻塞方式 
    flag = fcntl(0, F_GETFL); //先获取原来的 flag
    flag |= O_NONBLOCK; //将 O_NONBLOCK 标准添加到 flag
    fcntl(0, F_SETFL, flag); //重新设置 flag
    for ( ; ; ) {
        // /* 读鼠标 
        ret = read(fd, buf, sizeof(buf));
        if (0 < ret)
        printf("鼠标: 成功读取<%d>个字节数据\n", ret);
        // /* 读键盘 
        ret = read(0, buf, sizeof(buf));
        if (0 < ret)
        printf("键盘: 成功读取<%d>个字节数据\n", ret);
    }
    // /* 关闭文件 
    close(fd);
    exit(0);
}

原子学习笔记1——阻塞和非阻塞IO_第5张图片
注意:因为标准输入文件描述符(键盘)是从其父进程进程而来,并不是在我们的程序中调用 open()打开得到的,使用fcntl()函数将标准输入设置为非阻塞 I/O。

你可能感兴趣的:(#,Linux应用,学习,笔记)