MST

星途 面试题库

面试题:C语言在Linux下线程创建性能考量之专家题

在多核Linux系统中,使用C语言创建线程池来处理大量短时间任务。从线程创建开销、负载均衡、缓存一致性等方面分析如何设计一个高性能的线程池,并且给出关键代码片段和理论依据。
44.5万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

线程创建开销

  1. 预创建线程:在初始化线程池时,预先创建一定数量的线程,避免每次任务到来时动态创建线程的开销。动态创建线程涉及内核态与用户态的切换、内存分配等操作,开销较大。预创建线程可以将这些开销提前到线程池初始化阶段。
  2. 线程复用:任务完成后,线程不销毁而是返回线程池等待下一个任务,进一步减少线程创建开销。

负载均衡

  1. 任务队列:使用一个共享的任务队列,所有线程从该队列获取任务。可以采用先进先出(FIFO)策略,保证任务处理的顺序性,也可以根据任务优先级等因素进行调度。
  2. 工作窃取算法:当某个线程的任务队列为空时,它可以从其他繁忙线程的任务队列中窃取任务。这种方式能有效平衡线程间的负载,提高整体处理效率。

缓存一致性

  1. 数据局部性:尽量将相关数据和任务分配给同一线程处理,减少不同线程对共享数据的竞争,提高缓存命中率。例如,如果任务处理的数据有一定的空间或时间局部性,可以将这些相关任务分配给同一个线程。
  2. 减少锁的使用:锁会导致缓存一致性问题,因为锁操作可能会使缓存失效。尽量采用无锁数据结构(如无锁队列),或者使用细粒度锁来降低锁对缓存一致性的影响。

关键代码片段

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define THREADS 4
#define JOBS 10

// 任务结构体
typedef struct {
    void (*function)(void *);
    void *arg;
} job_t;

// 线程池结构体
typedef struct {
    job_t *queue;
    int front;
    int rear;
    int size;
    int count;
    pthread_mutex_t lock;
    pthread_cond_t not_empty;
    pthread_cond_t not_full;
} thread_pool_t;

// 线程函数
void* worker(void *arg) {
    thread_pool_t *pool = (thread_pool_t *)arg;
    while (1) {
        job_t job;
        pthread_mutex_lock(&pool->lock);
        while (pool->count == 0) {
            pthread_cond_wait(&pool->not_empty, &pool->lock);
        }
        job = pool->queue[pool->front];
        pool->front = (pool->front + 1) % pool->size;
        pool->count--;
        pthread_cond_signal(&pool->not_full);
        pthread_mutex_unlock(&pool->lock);

        (*job.function)(job.arg);
    }
    return NULL;
}

// 初始化线程池
thread_pool_t* create_thread_pool(int size) {
    thread_pool_t *pool = (thread_pool_t *)malloc(sizeof(thread_pool_t));
    pool->queue = (job_t *)malloc(size * sizeof(job_t));
    pool->front = 0;
    pool->rear = 0;
    pool->size = size;
    pool->count = 0;
    pthread_mutex_init(&pool->lock, NULL);
    pthread_cond_init(&pool->not_empty, NULL);
    pthread_cond_init(&pool->not_full, NULL);
    return pool;
}

// 添加任务到线程池
void add_job(thread_pool_t *pool, void (*function)(void *), void *arg) {
    pthread_mutex_lock(&pool->lock);
    while (pool->count == pool->size) {
        pthread_cond_wait(&pool->not_full, &pool->lock);
    }
    pool->queue[pool->rear] = (job_t){function, arg};
    pool->rear = (pool->rear + 1) % pool->size;
    pool->count++;
    pthread_cond_signal(&pool->not_empty);
    pthread_mutex_unlock(&pool->lock);
}

// 简单的任务函数示例
void task(void *arg) {
    int num = *((int *)arg);
    printf("Task %d is running.\n", num);
    sleep(1);
}

int main() {
    thread_pool_t *pool = create_thread_pool(JOBS);
    pthread_t threads[THREADS];

    for (int i = 0; i < THREADS; i++) {
        pthread_create(&threads[i], NULL, worker, pool);
    }

    int nums[JOBS];
    for (int i = 0; i < JOBS; i++) {
        nums[i] = i;
        add_job(pool, task, &nums[i]);
    }

    for (int i = 0; i < THREADS; i++) {
        pthread_join(threads[i], NULL);
    }

    free(pool->queue);
    free(pool);
    return 0;
}

理论依据

  1. 预创建线程和线程复用:通过减少线程创建和销毁的次数,降低系统开销,提高任务处理效率。
  2. 任务队列和工作窃取算法:任务队列实现了任务的集中管理和分配,工作窃取算法进一步平衡了线程间的负载,确保多核资源得到充分利用。
  3. 数据局部性和减少锁的使用:数据局部性提高了缓存命中率,减少锁的使用降低了缓存一致性问题带来的性能损耗,从而提升整体性能。