线程创建开销
- 预创建线程:在初始化线程池时,预先创建一定数量的线程,避免每次任务到来时动态创建线程的开销。动态创建线程涉及内核态与用户态的切换、内存分配等操作,开销较大。预创建线程可以将这些开销提前到线程池初始化阶段。
- 线程复用:任务完成后,线程不销毁而是返回线程池等待下一个任务,进一步减少线程创建开销。
负载均衡
- 任务队列:使用一个共享的任务队列,所有线程从该队列获取任务。可以采用先进先出(FIFO)策略,保证任务处理的顺序性,也可以根据任务优先级等因素进行调度。
- 工作窃取算法:当某个线程的任务队列为空时,它可以从其他繁忙线程的任务队列中窃取任务。这种方式能有效平衡线程间的负载,提高整体处理效率。
缓存一致性
- 数据局部性:尽量将相关数据和任务分配给同一线程处理,减少不同线程对共享数据的竞争,提高缓存命中率。例如,如果任务处理的数据有一定的空间或时间局部性,可以将这些相关任务分配给同一个线程。
- 减少锁的使用:锁会导致缓存一致性问题,因为锁操作可能会使缓存失效。尽量采用无锁数据结构(如无锁队列),或者使用细粒度锁来降低锁对缓存一致性的影响。
关键代码片段
#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;
}
理论依据
- 预创建线程和线程复用:通过减少线程创建和销毁的次数,降低系统开销,提高任务处理效率。
- 任务队列和工作窃取算法:任务队列实现了任务的集中管理和分配,工作窃取算法进一步平衡了线程间的负载,确保多核资源得到充分利用。
- 数据局部性和减少锁的使用:数据局部性提高了缓存命中率,减少锁的使用降低了缓存一致性问题带来的性能损耗,从而提升整体性能。