MST

星途 面试题库

面试题:C语言多线程性能优化及复杂场景应用

在一个C语言编写的高并发服务器程序中,有大量线程同时处理客户端请求。这些请求涉及到磁盘I/O、网络通信以及复杂的业务逻辑计算。请阐述如何从线程调度、资源分配、缓存机制以及异步I/O等方面对该程序进行性能优化。并且假设存在一种场景,部分请求需要等待其他请求完成后才能继续处理,这种情况下如何利用多线程的条件变量(condition variable)和信号量(semaphore)来设计合理的同步机制?请给出详细的设计思路和关键代码片段。
48.1万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

性能优化方面

  1. 线程调度
    • 使用线程池:创建一个固定大小的线程池,避免频繁创建和销毁线程的开销。例如,在初始化阶段,预先创建一定数量的线程并将它们放入线程池中等待任务。
    • 设置线程优先级:根据任务的紧急程度设置线程优先级。例如,对于网络通信相关的线程(处理实时数据传输)设置较高优先级,而对于一些非紧急的业务逻辑计算线程设置较低优先级。在Linux下可以使用pthread_setschedparam函数来设置线程优先级。
  2. 资源分配
    • 内存池:对于频繁申请和释放内存的场景(如处理客户端请求的缓冲区),使用内存池技术。预先分配一块较大的内存,然后从这块内存中分配小块内存供线程使用,当线程使用完毕后归还到内存池中,避免频繁的系统调用mallocfree
    • 文件描述符管理:对于磁盘I/O操作,合理分配文件描述符。可以采用复用文件描述符的方式,避免过多的文件描述符打开导致系统资源耗尽。例如,使用epoll(在Linux下)来管理多个文件描述符,高效地处理I/O事件。
  3. 缓存机制
    • 数据缓存:对于经常访问的数据(如配置信息、热点数据等),使用缓存。可以采用哈希表等数据结构来实现缓存,提高数据访问速度。例如,将常用的业务逻辑计算结果缓存起来,当下次相同请求到来时,直接从缓存中获取结果,减少重复计算。
    • I/O缓存:在磁盘I/O和网络通信中使用缓冲区。例如,在读取磁盘文件时,使用较大的缓冲区一次性读取较多数据,减少磁盘I/O次数;在网络通信中,使用发送缓冲区和接收缓冲区来提高数据传输效率。
  4. 异步I/O
    • 磁盘I/O异步化:在Linux下,可以使用aio系列函数(如aio_readaio_write)实现异步磁盘I/O。这样主线程不会阻塞等待磁盘I/O完成,可以继续处理其他任务。例如,当一个线程发起异步磁盘I/O请求后,主线程可以继续处理新的客户端请求,当I/O操作完成后,通过回调函数或事件通知机制来处理结果。
    • 网络异步I/O:使用异步网络I/O模型,如epoll的边缘触发模式(ET)结合非阻塞套接字。在这种模式下,当有数据到达时,系统只会通知一次,应用程序需要尽快处理数据,避免数据丢失。这样可以在高并发情况下更高效地处理网络请求。

同步机制设计

  1. 设计思路
    • 条件变量:用于线程间的同步,当某个条件满足时,等待该条件的线程被唤醒。在部分请求需要等待其他请求完成的场景下,当被依赖的请求完成时,通过条件变量通知等待的线程。
    • 信号量:可以用来控制对共享资源的访问数量。在这种场景下,可以使用信号量来控制依赖关系,例如,当一个请求完成后,释放一个信号量,等待的请求获取这个信号量后继续执行。
  2. 关键代码片段
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>

// 定义条件变量和互斥锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 定义信号量
sem_t sem;

// 全局变量,用于标记请求是否完成
int request_completed = 0;

// 被依赖的请求处理函数
void* dependent_request(void* arg) {
    // 模拟业务逻辑计算
    printf("Dependent request is processing...\n");
    // 完成业务逻辑后
    pthread_mutex_lock(&mutex);
    request_completed = 1;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
    sem_post(&sem);
    pthread_exit(NULL);
}

// 依赖其他请求的请求处理函数
void* dependent_on_request(void* arg) {
    pthread_mutex_lock(&mutex);
    while (!request_completed) {
        pthread_cond_wait(&cond, &mutex);
    }
    pthread_mutex_unlock(&mutex);
    sem_wait(&sem);
    // 继续处理依赖后的业务逻辑
    printf("Request that depends on others is continuing...\n");
    pthread_exit(NULL);
}

int main() {
    pthread_t tid1, tid2;
    sem_init(&sem, 0, 0);
    // 创建线程
    pthread_create(&tid1, NULL, dependent_request, NULL);
    pthread_create(&tid2, NULL, dependent_on_request, NULL);

    // 等待线程结束
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    sem_destroy(&sem);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

在上述代码中:

  • pthread_cond_t类型的cond是条件变量,pthread_mutex_t类型的mutex是互斥锁,用于保护共享资源request_completed
  • sem_t类型的sem是信号量。
  • dependent_request函数模拟被依赖的请求,当它完成时,通过条件变量cond通知等待的线程,并释放信号量。
  • dependent_on_request函数模拟依赖其他请求的请求,它通过条件变量等待request_completed变为1,并获取信号量后继续执行后续业务逻辑。