MST

星途 面试题库

面试题:C语言select机制的性能优化

在高并发场景下,C语言的select机制可能会出现性能瓶颈。请分析select机制在高并发时性能下降的原因,并提出至少两种针对性的优化方案,结合代码示例说明如何实现这些优化。
11.1万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

1. select机制在高并发时性能下降的原因

  • 文件描述符集合大小限制:在许多系统中,select所处理的文件描述符集合大小存在限制(如FD_SETSIZE,通常为1024),当高并发场景下需要处理的文件描述符数量超过此限制时,无法满足需求。
  • 线性扫描效率低select每次调用时,内核需要线性扫描整个文件描述符集合,检查哪些文件描述符已就绪。随着文件描述符数量增多,扫描时间会线性增长,导致性能下降。
  • 数据拷贝开销大:每次调用select时,需要将用户空间的文件描述符集合拷贝到内核空间,返回时又要将就绪的文件描述符集合从内核空间拷贝回用户空间,在高并发时这种数据拷贝的开销不容忽视。

2. 优化方案及代码示例

方案一:使用epoll

  • 原理epoll是Linux下高性能I/O事件通知机制,通过一个文件描述符管理多个文件描述符,内核使用红黑树管理监控的文件描述符,用链表管理就绪的文件描述符,因此在添加、删除文件描述符和获取就绪文件描述符时效率较高。
  • 代码示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>

#define MAX_EVENTS 10

int main() {
    int listen_fd, conn_fd;
    struct sockaddr_in servaddr;
    socklen_t clilen;
    struct epoll_event ev, events[MAX_EVENTS];
    int epoll_fd;

    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) {
        perror("socket");
        exit(1);
    }

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(8080);

    if (bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind");
        close(listen_fd);
        exit(1);
    }

    if (listen(listen_fd, 5) < 0) {
        perror("listen");
        close(listen_fd);
        exit(1);
    }

    epoll_fd = epoll_create1(0);
    if (epoll_fd < 0) {
        perror("epoll_create1");
        close(listen_fd);
        exit(1);
    }

    ev.events = EPOLLIN;
    ev.data.fd = listen_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev) < 0) {
        perror("epoll_ctl: listen_fd");
        close(listen_fd);
        close(epoll_fd);
        exit(1);
    }

    for (;;) {
        int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (nfds < 0) {
            perror("epoll_wait");
            break;
        }
        for (int n = 0; n < nfds; ++n) {
            if (events[n].data.fd == listen_fd) {
                clilen = sizeof(struct sockaddr_in);
                conn_fd = accept(listen_fd, (struct sockaddr *)&servaddr, &clilen);
                if (conn_fd < 0) {
                    perror("accept");
                    continue;
                }
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = conn_fd;
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, &ev) < 0) {
                    perror("epoll_ctl: conn_fd");
                    close(conn_fd);
                }
            } else {
                conn_fd = events[n].data.fd;
                char buf[1024];
                int nread = read(conn_fd, buf, sizeof(buf));
                if (nread < 0) {
                    if (errno == EAGAIN || errno == EWOULDBLOCK) {
                        continue;
                    } else {
                        perror("read");
                        close(conn_fd);
                    }
                } else if (nread == 0) {
                    close(conn_fd);
                } else {
                    buf[nread] = '\0';
                    printf("Received: %s\n", buf);
                }
            }
        }
    }
    close(listen_fd);
    close(epoll_fd);
    return 0;
}

方案二:使用kqueue(FreeBSD、macOS等系统)

  • 原理kqueue是FreeBSD和macOS等系统提供的高效事件通知机制,它通过一个内核对象管理多个事件,同样避免了线性扫描的开销,并且在事件管理和通知方面有较好的性能。
  • 代码示例
#include <sys/types.h>
#include <sys/event.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define MAX_EVENTS 10

int main() {
    int listen_fd, conn_fd;
    struct sockaddr_in servaddr;
    socklen_t clilen;
    int kq;
    struct kevent chlist[MAX_EVENTS], evlist[MAX_EVENTS];
    int nev;

    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) {
        perror("socket");
        exit(1);
    }

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(8080);

    if (bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind");
        close(listen_fd);
        exit(1);
    }

    if (listen(listen_fd, 5) < 0) {
        perror("listen");
        close(listen_fd);
        exit(1);
    }

    kq = kqueue();
    if (kq < 0) {
        perror("kqueue");
        close(listen_fd);
        exit(1);
    }

    EV_SET(&chlist[0], listen_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);

    for (;;) {
        nev = kevent(kq, chlist, 1, evlist, MAX_EVENTS, NULL);
        if (nev < 0) {
            perror("kevent");
            break;
        }
        for (int i = 0; i < nev; i++) {
            if (evlist[i].ident == listen_fd) {
                clilen = sizeof(struct sockaddr_in);
                conn_fd = accept(listen_fd, (struct sockaddr *)&servaddr, &clilen);
                if (conn_fd < 0) {
                    perror("accept");
                    continue;
                }
                EV_SET(&chlist[0], conn_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
            } else {
                conn_fd = evlist[i].ident;
                char buf[1024];
                int nread = read(conn_fd, buf, sizeof(buf));
                if (nread < 0) {
                    if (errno == EAGAIN || errno == EWOULDBLOCK) {
                        continue;
                    } else {
                        perror("read");
                        close(conn_fd);
                    }
                } else if (nread == 0) {
                    close(conn_fd);
                } else {
                    buf[nread] = '\0';
                    printf("Received: %s\n", buf);
                }
            }
        }
    }
    close(listen_fd);
    close(kq);
    return 0;
}