zoukankan      html  css  js  c++  java
  • 多进程/线程select同一文件问题

    一、多进程select
    这个是一个不太常见的场景,但是作为探讨性话题,大家可以在这里尽情YY一下,就像YY我们达到共产主义一样,想想会是什么情景,当然,还是这里讨论的问题更靠谱一些。
    根据select的语义,就是进程来同时等待若干个文件可读/可写/错误状态,直到指定时间结束,这个我想大家都是明白的。现在的场景是对于一个文件,例如一个socket,控制台等设备的等待同时有多个,例如A进程,B进程两个都在select这个文件,那么当这个文件准备就绪的时候,两个线程是否都会被唤醒还是只有一个唤醒?唤醒之后它们谁会读到这个数据?
    二、select等待
    do_select--->>poll_initwait--->>>__pollwait--->>init_waitqueue_entry

    static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
    {
        q->flags = 0;
        q->private = p;
        q->func = default_wake_function;
    }
    这里是对线程的等待实体的初始化,之后唤醒的时候将会使用到里面的数据结构,暂且不表。
    以大家熟悉的命名管道(也是比较容易测试的一种文件)为例,当它在线程在select可读的时候,如果有人对管道进行了写入操作,那么执行的代码为pipe_write--->>>wake_up_interruptible(&pipe->wait)---->>>__wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)--->>>__wake_up_common

    /*
     * The core wakeup function.  Non-exclusive wakeups (nr_exclusive == 0) just
     * wake everything up.  If it's an exclusive wakeup (nr_exclusive == small +ve
     * number) then we wake all the non-exclusive tasks and one exclusive task.
     *
     * There are circumstances in which we can try to wake a task which has already
     * started to run but is not in state TASK_RUNNING.  try_to_wake_up() returns
     * zero in this (rare) case, and we handle it by continuing to scan the queue.
     */
    static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
                     int nr_exclusive, int sync, void *key)
    {
        struct list_head *tmp, *next;

        list_for_each_safe(tmp, next, &q->task_list) {
            wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);
            unsigned flags = curr->flags;

            if (curr->func(curr, mode, sync, key) &&
                    (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
                break;
        }
    }
    其中对于等待队列的遍历是非常简明扼要的,如果说一个等待者比较喜欢吃独食,那么他在等待的标志位设置WQ_FLAG_EXCLUSIVE标志,表示只唤醒我一个,当然还要和传入的互斥唤醒个数也有关系,即使唤醒标志中设置了自己为互斥,如果参数中nc_exclusive要求多个,那么这个标志只能委屈一下了(即强*民意)。对于我们这里分析的场景,select的唤醒是非常容易相处的(easy-going),所以这个等待队列上所有的等待者都将会被唤醒,皆大欢喜。
    三、所有select都会被唤醒返回吗
    从上面看,是这样的,因为等待队列上所有的等待者都会被唤醒。本着蛋疼的精神,我试了一下,现象并非如此(准确的说,并不总是如此),也就是有时只有一个进程的select系统调用返回,另一个线程的select依然在阻塞,压根都没有返回到用户态,当然我的测试程序是在select返回之后从这个文件中读取数据。所以再审视一下do_select函数的实现
        for (;;) {
            unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
            long __timeout;

            set_current_state(TASK_INTERRUPTIBLE);
        for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
        for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {
           if (file) {
                        f_op = file->f_op;
                        mask = DEFAULT_POLLMASK;
                        if (f_op && f_op->poll)
                            mask = (*f_op->poll)(file, retval ? NULL : wait);
    ……
            __timeout = schedule_timeout(__timeout);
            if (*timeout >= 0)
                *timeout += __timeout;
        }
    这里要说的是,并不是说waiter被从schedule_timeout唤醒之后就功德圆满了,因为这里重重循环会迫使它再掉头去poll各个指定等待的文件里去咨询调查(poll)一下。现在精彩的部分来了:
    唤醒操作一次会唤醒所有的等待者,它们将会同时到达可运行状态,但是对于单核系统来说,它们是顺序执行的。假设说A比较幸运,最早运行,它成功从select返回到用户态,然后A线程毫不客气,一下子读光了文件中的内容,然后满意而去,而B等了一段时间才会获得执行权,当它执行自己迟来的poll检测时,会发现那个文件并没有准备好,因为数据已经被A抢先消耗掉了,所以B空欢喜了一场,继续执行上面的schedule_time,继续等待,在用户态看来它没有从内核返回(有兴趣的同学可以看一下,它的调度次数应该会加一)。
    四、再次等待时等待实体wait_queue_t何时回收
    在进行唤醒的时候,这个实体并不会从从等待队列上删除,所以即使被唤醒了,它依然在目标文件的等待队列中,所以没有关系。在循环体中
    mask = (*f_op->poll)(file, retval ? NULL : wait);
    也可以看到,如果被唤醒,那么retval不等于零,所以之后poll的时候传入的实体为空,即复用上次对象,这里只是进行一次确认性检查。
    五、推广
    这里还可以推广到多个进程select,多个进程read同一个文件的情况。
    六、todo
    写个测试程序展示一下我想描述的内容。

  • 相关阅读:
    网站图片轮播效果
    图片处理类
    字符串处理帮助类
    css3高级选择器
    JQuery选择器大全
    ASCII码表
    jQuery选择器大全
    OpenFileDialog无法弹出的解决方法
    socket学习目录
    ps-抠图
  • 原文地址:https://www.cnblogs.com/tsecer/p/10486198.html
Copyright © 2011-2022 走看看