• select相关


    1. 定义

    同步IO多路复用。

    select(2)pselect(2) 的区别:

    • 时间精度不同,select(2)struct timeval,精确到us,pselect(2)struct timespec
      ,精确到ns
    • select(2) 会更新 timeout ,提示还剩下多长时间,pselect() 不会更新参数
    • select(2) 不会捕获信号,没有 sigmask 参数
    /* According to POSIX.1-2001 */
    #include <sys/select.h>
    
    /* According to earlier standards */
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    struct timeval {
        time_t tv_sec;    /* seconds */
        long tv_usec;     /* microseconds */
    };
    
    int select(int nfds, fd_set *readfds, fd_set *writefds,
                fd_set *exceptfds, struct timeval *timeout);
    
    void FD_CLR(int fd, fd_set *set);
    int  FD_ISSET(int fd, fd_set *set);
    void FD_SET(int fd, fd_set *set);
    void FD_ZERO(fd_set *set);
    
    #include <sys/select.h>
    
    struct timespec {
        long tv_sec;    /* seconds */
        long tv_nsec;   /* nanoseconds */
    };
    int pselect(int nfds, fd_set *readfds, fd_set *writefds,
                fd_set *exceptfds, const struct timespec *timeout,
                const sigset_t *sigmask);
    
    Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
    
    pselect(): _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
    

    2. 使用

    2.1. 输入

    监听三组相互独立的fd组:可读事件组,可写事件组,异常事件组。FD_()函数族可以控制fd_set。返回也是在这些地方,因此如果再循环中使用select()时,需要每次都重新初始化希望监听的组。返回的fd在可读事件组中,表示该fd上可以立刻读出数据;在可写事件组中,表示该fd有空间可以写。

    nfds是三组中fd编号最大的值再+1。

    timeout控制阻塞时间,NULL表示一直阻塞,0表示不阻塞,立刻返回。

    sigmask不为NULL时,pselect(2)会先将当前监听的信号组保存,替换成sigmask指向的信号组,然后进行select,返回后再恢复之前的信号组。也就是说,这样的调用:

    ready = pselect(nfds, &readfds, &writefds, &exceptfds, timeout, &sigmask);
    

    会等价于 原子性 的执行:

    sigset_t origmask;
    
    pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);
    ready = select(nfds, &readfds, &writefds, &exceptfds, timeout);
    pthread_sigmask(SIG_SETMASK, &origmask, NULL);
    

    2.2. 输出

    大于0:表示三个返回的fd组中fd的总个数,需要用FD_ISSET()检查某个fd是否有事件返回
    0: 超时
    -1: 失败,并设置errno,此时fd组和timeout未定义,不能使用

    errno:

    • EBADF: 输入的fd组中有无效的fd(fd已关闭,或者已经发生了错误)。
    • EINTR: 产生信号
    • EINVAL: nfds是负数或者timeout无效
    • ENOMEM: 没有内存创建内部表

    3. 为什么要有pselect(2)

    UNIX网络编程给了个例子。这个程序的SIGINT信号处理函数设置全局变量intr_flag并返回,然后程序主逻辑检查intr_flag是否设置,如果设置了就进行处理。如果主逻辑阻塞在select()调用,此时产生了SIGINT信号,select()会返回EINTR错误,返回之后,可以继续检查intr_flag是否设置了。代码大致长这样:

    if (intr_flag)      // 1
        handle_intr();  // 处理SIGINT信号
    if ((nready = select(...)) < 0) {   // 2
        if (errno == EINTR) {
            if (intr_flag)
                handle_intr();
        }
    }
    

    但有个问题,如果主逻辑在测试intr_flag(1)和调用select(2) 之间有信号发生的话,并且如果select永远阻塞,该信号将丢失。使用pselect就可以安全处理这种情况:

    sigset_t newmask, oldmask, zeromask;
    
    sigemptyset(&zeromask);
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGINT);
    
    sigprocmask(SIG_BLOCK, &newmask, &oldmask);  // block SIGINT
    if (intr_flag)      // 1
        handle_intr();
    if ((nready = pselect(..., &zeromask)) < 0) {   // 2
        if (errno == EINTR) {
            if (intr_flag)
                handle_intr();
        }
        ...
    }
    

    在测试intr_flag之前,阻塞掉SIGINT,当调用pselect时,将阻塞的信号集替换为空集zeromask,解除对SIGINT的屏蔽,pselect返回时,又会将SIGINT屏蔽掉,这样,SIGINT信号只会在(1)之前和pselect()被阻塞时(2)捕获,保证不会错过对信号的处理。

    man 2 select_tut中有个完整些的例子,可以看看。

    4. 多线程

    如果正在被select()监听的fd在另一个线程中被关闭,结果无定义。一些UNIX系统上,select()解除阻塞,立即返回,并且表明该fd上有事件发生(但接下来对该fd的操作可能失败,因为已经关闭了。除非另一个线程在select()返回和对fd操作之间重新打开了这个fd)。linux上,另一个线程关闭fd对select()无影响。总体来说,别在多个线程上同时处理同一个fd。

    select()返回可读事件后,后续的读操作仍有可能阻塞,比如数据已经到了,但上层检查的时候,因为校验和错误而丢掉该数据。因此最好是配合非阻塞IO操作。

    5. 例子

    这个是 manual 中给的例子,监听stdin是否有输入

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    int
    main(void)
    {
        fd_set rfds;
        struct timeval tv;
        int retval;
    
        /* Watch stdin (fd 0) to see when it has input. */
        FD_ZERO(&rfds);
        FD_SET(0, &rfds);
    
        /* Wait up to five seconds. */
        tv.tv_sec = 5;
        tv.tv_usec = 0;
    
        retval = select(1, &rfds, NULL, NULL, &tv);
        /* Don't rely on the value of tv now! */
    
        if (retval == -1)
            perror("select()");
        else if (retval)
            printf("Data is available now.
    ");
            /* FD_ISSET(0, &rfds) will be true. */
        else
            printf("No data within five seconds.
    ");
    
        exit(EXIT_SUCCESS);
    }
    
  • 相关阅读:
    【5】TensorFlow光速入门-图片分类完整代码
    【4】TensorFlow光速入门-保存模型及加载模型并使用
    科研数据库结构
    高并发请求的缓存设计策略
    iOS-KVC的原理
    iOS-KVO的原理
    Kafka too many open files问题解决
    VLOOKUP函数-Excel
    arcgis sql 字符串替换
    ArcGIS矢量转栅格再发布切片服务,还是直接发布切片服务?有何区别?
  • 原文地址:https://www.cnblogs.com/suntus/p/14903756.html
Copyright © 2020-2023  润新知