面试题答案
一键面试优化思路
- 减少进程创建开销:prefork模型预先创建一定数量子进程,避免每次请求都创建新进程带来的昂贵开销。但要合理设置预创建子进程数量,过多会浪费资源,过少则无法充分应对并发请求。
- 负载均衡:确保每个子进程均匀处理请求,避免部分子进程负载过重,部分空闲。可以采用多种负载均衡算法,如轮询(Round - Robin)、加权轮询(Weighted Round - Robin,考虑每个子进程处理能力不同给予不同权重)、最少连接数优先等。
- 资源复用:对于一些常用资源,如数据库连接、文件描述符等,在子进程间复用,减少资源重复创建和销毁的开销。例如,建立连接池,子进程从连接池中获取和归还连接。
- 内存管理优化:避免子进程内存泄漏,合理分配和释放内存。对于频繁使用的小块内存,可以使用内存池技术,减少内存碎片,提高内存分配效率。
- 异步I/O:使用异步I/O操作,如
aio_read
、aio_write
等,让子进程在进行I/O操作时不必阻塞等待,提高系统并发处理能力。同时结合事件驱动机制,如使用epoll
(Linux下高效的I/O多路复用技术)来监控I/O事件,及时处理就绪的I/O请求。
C语言编程要点
- 进程管理
- 使用
fork
函数创建子进程。在父进程中管理子进程状态,例如通过waitpid
函数等待子进程结束,处理子进程的退出状态,防止产生僵尸进程。
pid_t pid = fork(); if (pid == 0) { // 子进程逻辑 exit(EXIT_SUCCESS); } else if (pid > 0) { // 父进程逻辑 int status; pid_t wpid = waitpid(pid, &status, 0); if (wpid == -1) { perror("waitpid error"); } } else { perror("fork error"); }
- 使用
- 负载均衡实现
- 轮询算法:父进程维护一个子进程索引,每次有新请求,将请求分配给索引对应的子进程,然后索引自增并取模(子进程数量)。
int child_index = 0; // 分配请求给子进程 pid_t child_pid = child_pids[child_index]; // 传递请求相关信息给子进程(例如通过管道等方式) child_index = (child_index + 1) % num_children;
- 资源复用(以数据库连接池为例)
- 使用结构体表示连接池,包含连接数组、连接数量、已用连接数量等信息。
typedef struct { void* connections[MAX_CONNECTIONS]; int count; int in_use; } ConnectionPool; // 初始化连接池 ConnectionPool* create_connection_pool() { ConnectionPool* pool = (ConnectionPool*)malloc(sizeof(ConnectionPool)); pool->count = MAX_CONNECTIONS; pool->in_use = 0; for (int i = 0; i < MAX_CONNECTIONS; i++) { pool->connections[i] = create_database_connection(); } return pool; } // 获取连接 void* get_connection(ConnectionPool* pool) { if (pool->in_use >= pool->count) { return NULL; } pool->in_use++; return pool->connections[pool->in_use - 1]; } // 归还连接 void return_connection(ConnectionPool* pool, void* conn) { for (int i = 0; i < pool->count; i++) { if (pool->connections[i] == conn) { pool->in_use--; break; } } }
- 内存管理
- 使用
malloc
分配内存,使用free
释放内存,确保内存正确释放,避免内存泄漏。对于内存池的实现,需要更复杂的内存分配和释放逻辑,例如维护空闲内存块链表等。
int* data = (int*)malloc(sizeof(int) * size); if (data == NULL) { perror("malloc error"); return; } // 使用data free(data);
- 使用
- 异步I/O与事件驱动
- 异步I/O:以
aio_read
为例,首先构造aiocb
结构体,设置相关参数,然后调用aio_read
发起异步读操作。通过aio_suspend
或aio_error
等函数检查操作状态。
#include <aio.h> struct aiocb my_aiocb; memset(&my_aiocb, 0, sizeof(struct aiocb)); my_aiocb.aio_fildes = fd; my_aiocb.aio_buf = buffer; my_aiocb.aio_nbytes = buffer_size; my_aiocb.aio_offset = 0; if (aio_read(&my_aiocb) == -1) { perror("aio_read error"); } // 检查操作状态 int error = aio_error(&my_aiocb); if (error == 0) { ssize_t bytes_read = aio_return(&my_aiocb); }
- 事件驱动(
epoll
):创建epoll
实例,使用epoll_ctl
添加或删除监控的文件描述符,通过epoll_wait
等待事件发生。
int epoll_fd = epoll_create1(0); if (epoll_fd == -1) { perror("epoll_create1 error"); return; } struct epoll_event event; event.data.fd = socket_fd; event.events = EPOLLIN | EPOLLET; // 边缘触发模式 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &event) == -1) { perror("epoll_ctl error"); close(epoll_fd); return; } struct epoll_event events[MAX_EVENTS]; int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); for (int i = 0; i < num_events; i++) { int fd = events[i].data.fd; if (events[i].events & EPOLLIN) { // 处理读事件 } } close(epoll_fd);
- 异步I/O:以