MST

星途 面试题库

面试题:Linux C语言线程池的任务调度与资源管理

在Linux环境下用C语言实现线程池时,如何合理地进行任务调度以避免线程饥饿问题?同时,当线程池中的线程数量动态调整时,怎样有效地管理系统资源(如文件描述符、内存等),防止资源泄漏?
23.7万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

避免线程饥饿的任务调度

  1. 公平调度算法
    • 轮询调度(Round - Robin):维护一个任务队列,每个线程按顺序依次从队列中获取任务。例如,创建一个环形队列,线程每次处理完任务后,从队列的下一个位置获取新任务。
    // 假设任务结构体
    typedef struct Task {
        void (*func)(void*);
        void *arg;
    } Task;
    
    Task taskQueue[QUEUE_SIZE];
    int head = 0;
    int tail = 0;
    
    Task getTask() {
        Task task = taskQueue[head];
        head = (head + 1) % QUEUE_SIZE;
        return task;
    }
    
    • 时间片轮转调度:为每个任务分配一定的执行时间片。当时间片用完后,将任务放回队列末尾等待下次调度。可以使用setitimer函数来实现时间片控制。
    struct itimerval timer;
    timer.it_value.tv_sec = 0;
    timer.it_value.tv_usec = TIME_SLICE_USEC;
    setitimer(ITIMER_VIRTUAL, &timer, NULL);
    // 处理时间片到期信号
    signal(SIGVTALRM, timeSliceExpiredHandler);
    
  2. 优先级调度
    • 为任务设置优先级字段。任务入队时,根据优先级插入到合适的位置。例如,可以使用堆数据结构来维护任务队列,高优先级任务在堆顶。
    // 基于堆的优先级队列实现
    void insertTask(Task task, int priority) {
        // 插入任务到堆中合适位置
    }
    
    Task getHighestPriorityTask() {
        // 从堆顶获取最高优先级任务
    }
    
    • 定期调整任务优先级,避免高优先级任务一直占用资源,导致低优先级任务饥饿。例如,每隔一段时间,将所有任务的优先级降低一定值。

动态调整线程数量时的资源管理

  1. 文件描述符管理
    • 线程本地存储(TLS):使用pthread_key_create创建线程本地存储键,每个线程在需要打开文件描述符时,将其存储在自己的线程本地存储中。
    pthread_key_t fileDescriptorKey;
    pthread_key_create(&fileDescriptorKey, freeFileDescriptor);
    
    void* threadFunction(void* arg) {
        int fd = open("example.txt", O_RDONLY);
        pthread_setspecific(fileDescriptorKey, (void *)fd);
        // 线程处理任务
        int localFd = (int)pthread_getspecific(fileDescriptorKey);
        close(localFd);
    }
    
    • 资源池:创建一个文件描述符资源池,线程从资源池中获取和归还文件描述符。可以使用互斥锁保护资源池的访问。
    int fileDescriptorPool[POOL_SIZE];
    pthread_mutex_t poolMutex;
    pthread_mutex_init(&poolMutex, NULL);
    
    int getFileDescriptorFromPool() {
        pthread_mutex_lock(&poolMutex);
        for (int i = 0; i < POOL_SIZE; i++) {
            if (fileDescriptorPool[i]!= -1) {
                int fd = fileDescriptorPool[i];
                fileDescriptorPool[i] = -1;
                pthread_mutex_unlock(&poolMutex);
                return fd;
            }
        }
        pthread_mutex_unlock(&poolMutex);
        return -1; // 资源池为空
    }
    
    void returnFileDescriptorToPool(int fd) {
        pthread_mutex_lock(&poolMutex);
        for (int i = 0; i < POOL_SIZE; i++) {
            if (fileDescriptorPool[i] == -1) {
                fileDescriptorPool[i] = fd;
                break;
            }
        }
        pthread_mutex_unlock(&poolMutex);
    }
    
  2. 内存管理
    • 内存池:创建一个内存池,线程从内存池中分配和释放内存。使用mallocfree管理内存池本身,线程在内存池中分配小块内存。
    char *memoryPool = (char *)malloc(POOL_SIZE);
    int usedBytes = 0;
    
    void* allocateFromMemoryPool(size_t size) {
        if (usedBytes + size <= POOL_SIZE) {
            void *ptr = memoryPool + usedBytes;
            usedBytes += size;
            return ptr;
        }
        return NULL;
    }
    
    void freeInMemoryPool(void* ptr) {
        // 简单实现,这里假设内存池是连续分配且按顺序使用,实际需更复杂逻辑
        int offset = (char *)ptr - memoryPool;
        if (offset >= 0 && offset < usedBytes) {
            usedBytes -= (usedBytes - offset);
        }
    }
    
    • 智能指针(模拟):在任务结构体中,为需要管理的内存分配一个引用计数。当任务完成时,减少引用计数,当引用计数为0时,释放内存。
    typedef struct MemoryWrapper {
        void *ptr;
        int refCount;
    } MemoryWrapper;
    
    MemoryWrapper* createMemoryWrapper(void *ptr) {
        MemoryWrapper *wrapper = (MemoryWrapper *)malloc(sizeof(MemoryWrapper));
        wrapper->ptr = ptr;
        wrapper->refCount = 1;
        return wrapper;
    }
    
    void increaseRefCount(MemoryWrapper *wrapper) {
        wrapper->refCount++;
    }
    
    void decreaseRefCount(MemoryWrapper *wrapper) {
        wrapper->refCount--;
        if (wrapper->refCount == 0) {
            free(wrapper->ptr);
            free(wrapper);
        }
    }