Linux系统多进程多路复用唤醒冲突如何解决

Linux 对于 accept(2) 的惊群(thundering herd)问题, 早已解决。目前许多人也把这种现象称为新的惊群:用多路复用模型时, 不同的进程监控的文件描述符集合的交集不为空, 等这个交集的某个文件IO事件触发后, 内核将的多个监控了这个io且阻塞在 select(2), poll(2) 或 epoll_wait(2) 的进程唤醒。但严格来说, 这种现象不叫惊群(thundering herd), 而是冲突(collision). 对于内核来说, 唤醒所有监控这一IO事件的进程是合理的。这是因为: select/poll/epoll 不同与 accept, 它们监控的文件描述符是可以被多个进程同时处理的, 比如一个进程只读取这个文件句柄一小部分数据, 另一进程读剩余部分。而 accept 处理的套接字是互斥的,一个套接字不能被两个进程 accept.

我注意到,对这种 select/poll/epoll 冲突的理解存在许多误区,比如有人都用如下类似的代码模拟select冲突(网上搜 select 惊群或 epoll 惊群有真相):

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <strings.h>
#include <arpa/inet.h>

void worker_hander(int listenfd)
{
    fd_set rset;
    int connfd, ret;

    printf("worker pid#%d is waiting for connection...n", getpid());
    for (;;) {
        FD_ZERO(&rset);
        FD_SET(listenfd,&rset);
        ret = select(listenfd+1,&rset,NULL,NULL,NULL);
        if(ret < 0)
            perror("select");
        else if(ret > 0 && FD_ISSET(listenfd, &rset)) {
            printf("worker pid#%d 's listenfd is readablen",
                    getpid());
            connfd = accept(listenfd, NULL, 0);
            if(connfd < 0) {
                perror("accept error");
                continue;
            }
            printf("worker pid#%d create a new connection...n",
                    getpid());
            sleep(1);
            close(connfd);
        }
    }
}

static int fd_set_noblock(int fd)
{
    int flags;

    flags = fcntl(fd, F_GETFL);
    if (flags == -1)
        return -1;
    flags |= O_NONBLOCK;
    flags = fcntl(fd, F_SETFL, flags);
    return flags;
}

int main(int argc,char*argv[])
{
    int listenfd;
    struct sockaddr_in servaddr;
    int sock_opt = 1;

    listenfd = socket(AF_INET,SOCK_STREAM,0);
    if (listenfd < 0) {
        perror("socket");
        exit(1);
    }
    fd_set_noblock(listenfd);
    if ((setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void *)&sock_opt,
            sizeof(sock_opt))) < 0) {
        perror("setsockopt");
        exit(1);
    }
    bzero(&servaddr, sizeof servaddr);
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(1234);
    bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    listen(listenfd, 10);

    pid_t pid;
    pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(1);
    } else if (pid == 0)
        worker_hander(listenfd);
    worker_hander(listenfd);
    return 0;
}

编译后用先运行以上的服务端, 客户端可以用 netcat 模拟连接:

nc 127.0.0.1 1234

以上代码是两个进程同时监控同一个文件描述符,返回的结果基本是只有一个select返回。于是试验人认为"并不是将所有工作进程全部唤醒,而只是唤醒了一部分".

这个错误的认识在于没有理解唤醒的含义. 并不是要从 select(2) 返回才叫唤醒。

一个进程在等待的io事件发生之前,内核会为这个进程描述符的state字段设置 TASK_INTERRUPTIBLE 状态, 此时进程描述符位于等待队列中。一旦等待的事件发生后,进程就会被唤醒,进程描述符就会被移到运行队列中, 发生进程切换时,内核进程调度器会根据调度策略从运行队列选择一个进程执行。

因此,上述程序实际上唤醒了所有的两个进程, 只不过先被调度的那个进程 select(2) 返回后, 如果执行到accept(2) 也没有发生进程切换,把IO事件处理掉了。而等到后调度的那个进程执行时,select(2) 里面已经没有这个IO事件了, 内核检测这个进程没有监控的事件发生,会把这个进程继续放到等待队列里面去, select(2) 并没有返回。 这种情况的概率是非常大的。另一种概率很小的情况是:先被调度的进程执行到 accept(2) 就发生了进程切换,而在下一次运行前,调度器启动了后一个进程,这样的话,后一个进程也将会从select(2)返回。

后一种情况很不容易发生, 在 accetp(2) 之前插入 usleep(3) 或 sleep(3) 就可以提高发生的概率了。

内核唤醒进程又不能让这个进程执行, 再次把它移动到等待队列, 造成了一定的开销浪费。nginx 是这样处理的: 用一个管理进程管理多个工作进程的多路复用。工作进程在epoll_wait(2)前向管理进程申请锁, 确保同一时刻, 多个进程在epoll监听的文件描述符集合的交集为空。

时间: 2024-09-11 02:19:47

Linux系统多进程多路复用唤醒冲突如何解决的相关文章

Linux系统无法访问MySQL数据库怎么解决

  1.问题及异常 ThreadPoolAsynchronousRunner - com.mchange.v2.async.ThreadPoolAsynchronousRunner$DeadlockDetector@75d634ea -- APPARENT DEADLOCK!!! Complete Status: Managed Threads: 3 Active Threads: 3 Active Tasks: 2.查找原因 费劲周知,确定是MySQL权限的问题 3.解决过程 1> mysql

linux系统中svn自动更新出错解决办法

linux/unix下svn自动更新出错解决: post-commit hook failed (exit code 255) with no output. linux/unix下要实现svn提交后自动更新到测试服务器,添加hook即可/usr/svn/mulu/hooks/post-commit 不带后缀 设置权限为可执行 chmod 777 /usr/svn/baoming/hooks/post-commit    代码如下 复制代码 #!/bin/sh WEB="/usr/home/mu

Linux系统出现乱码问题的终极解决方法

在linux下搭建网站的时候,乱码问题困扰了我差不多一个星期,后来终于解决了. 我的体会就是:应用必须和数据库的字符集(编码)相一致. 具体地说,就是,如果你想使用gb2312编码,那就要保证:每一个jsp文件都是以gb2312编码存储的(在保存文件的时候选择gb2312编码就可以了),另外,文件头还要定义;关于数据库,创建数据库时就要定义编码,例如:create database mysql default character set gb2312 collate gb2312_chinese

linux系统SSH访问被拒绝网络故障的解决

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 在最近碰到了网站遭遇挂黑链事件被百度惩罚,流量大幅减少之后,笔者开始真切认识到网站安全问题是重中之重,一旦被挂黑链前期的辛苦会在顷刻之间全部付诸东流.与绝大部分的站长一样,在网站建设的时候,笔者选择了windows 2003作为服务器的操作系统,最大的考虑因素是由于这个操作系统简单易用.近期在一个做IT技术的朋友建议下,我尝试着将网站&quo

Linux系统无法使用访问MySQL解决方法

  Linux系统无法使用访问MySQL解决方法.MySQL是最为常见的关系型数据库管理系统,不过有不少用户在使用过程中也会遇到一些小问题,有Linux系统用户发现,在Linux系统无法访问MySQL,为什么会造成这样原因呢?又要怎么解决呢?让我们一起来寻找答案吧. Linux 1.问题及异常 ThreadPoolAsynchronousRunner - com.mchange.v2.async.ThreadPoolAsynchronousRunner$DeadlockDetector@75d6

Linux系统下无法访问mysql解决方法

  mysql是一个关系型数据库管理系统,但最近有用户反映,在Linux系统下无法访问mysql,相信不少用户都有遇到过这个问题,这是怎么回事呢?Linux系统下无法访问mysql该怎么办呢?下面我们一起来看看解决方法. 1.问题及异常 ThreadPoolAsynchronousRunner - com.mchange.v2.async.ThreadPoolAsynchronousRunner$DeadlockDetector@75d634ea -- APPARENT DEADLOCK!!!

阿里云使用Linux系统有哪些问题

ECS Linux服务器发现未授权登录用户 ECS Linux服务器配置yum源 ECS Linux下解压rar格式的压缩文件 Linux查看实时带宽流量情况 ECS Linux开启swap(虚拟内存) linux磁盘空间用满的处理方法 ECS Linux服务器出现死机或者卡顿现象分析 ECS Linux系统Mysql备份的导入导出 ECS Linux系统查看编码 ECS Linux程序异常退出提示out of memory ECS Linux如何查看端口状态 如何分析php-cgi进程占用cp

要不要双引导Linux系统的七个理由

计算机当中最重要的组件之一就是操作系统.事实上,强大的操作系统应当具备软件兼容性,并能够顺畅实现硬件与软件之间的交互.对于大多数用户而言,Linux 加 Windows 或者 Linux 加 Mac OS 往往是最理想的组合. 日常使用中,我们可以在同一台设备上使用双系统.Windows 与 Linux 可谓各有所长.Linux 拥有出色的可定制能力.安全性优势.具备专门的开源社区且大多数为免费版本.Windows 或者 Mac OS 也有着自己的拥护者,它们的优势在于具备更多原生应用及更低操作

《Linux系统编程(第2版)》——2.10 I/O多路复用

2.10 I/O多路复用 应用通常需要在多个文件描述符上阻塞:在键盘输入(stdin).进程间通信以及很多文件之间协调I/O.基于事件驱动的图形用户界面(GUI)应用可能会和成百上千个事件的主循环竞争[5]. 如果不使用线程,而是独立处理每个文件描述符,单个进程无法同时在多个文件描述符上阻塞.只要这些描述符已经有数据可读写,也可以采用多个文件描述符的方式.但是,要是有个文件描述符数据还没有准备好--比如发送了read()调用,但是还没有任何数据--进程会阻塞,而且无法对其他的文件描述符提供服务.