Reactor 模式与工作线程池
1.1 thread_pool.h
#ifndef _THREAD_POOL_H_
#define _THREAD_POOL_H_
#include <pthread.h>
#include <stdint.h>
#include <stddef.h>
#include "../connection.h"
//线程池 = 预先创建一堆工作线程 + 任务队列分发 = 避免频繁创建/销毁线程的开销
//链表本质:数据 + 下一个节点的地址
typedef struct Task{
Conn *conn;
uint16_t type;
void *payload; // 消息体数据(必须是堆内存):epoll 缓冲区会被覆盖,必须复制一份
struct Task *next; // 下一个任务(链表指针),内部必须用 struct Task
} Task; //结构体内部引用自己时必须用struct Task(结构体标签)
//结构体要「自引用」?:链表节点要指向同类型的下一个节点
typedef struct { //链表节点必须用 malloc(堆内存),Task *t = malloc(sizeof(Task));
pthread_mutex_t lock; // 互斥锁:保护任务队列(多线程同时操作队列),保护共享数据,同一时间只允许一个线程访问
pthread_cond_t notify; // 条件变量:让线程「等某个条件成立」再继续执行
Task *head; // 任务链表头
Task *tail; // 任务链表尾
pthread_t *threads; // 工作线程数组
int thread_count; // 线程数量
int shutdown; // 线程池关闭标志
} ThreadPool;
ThreadPool* thread_pool_create(int thread_count);
void thread_pool_submit(
ThreadPool *pool,
Conn *conn,
uint16_t type,
void *payload,
size_t payload_len);
#endif
1.2 thread_pool.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "thread_pool.h"
#include "../dispatcher.h"
static void* worker_routine(void *arg){
ThreadPool *pool = (ThreadPool *)arg;
while (1) {
pthread_mutex_lock(&(pool->lock)); // 1. 抢锁
// 2. 如果队列为空且没有关停,就陷入沉睡,等待老板叫醒
while (pool->head == NULL && !pool->shutdown) {
pthread_cond_wait(&(pool->notify), &(pool->lock));
}
if (pool->shutdown) {
pthread_mutex_unlock(&(pool->lock));
pthread_exit(NULL);
}
// 3. 领任务 (从队列头部摘取)
Task *task = pool->head;
if (task) {
pool->head = task->next;
if (pool->head == NULL) pool->tail = NULL;
}
pthread_mutex_unlock(&(pool->lock)); // 4. 拿完任务,马上释放锁,让其他兄弟去抢
// 5. 干活!调用业务分发器 (这部分耗时再长,也不会卡住主线程了)
if (task) {
// 真正干活的地方
dispatch_message(task->conn, task->type, task->payload);
// 干完活,释放老板给这个任务分配的内存
free(task->payload);
free(task);
}
}
return NULL;
}
// 初始化线程池
ThreadPool* thread_pool_create(int thread_count) {
ThreadPool *pool = (ThreadPool *)malloc(sizeof(ThreadPool));
pool->thread_count = thread_count;
pool->shutdown = 0;
pool->head = NULL;
pool->tail = NULL;
pthread_mutex_init(&(pool->lock), NULL);
pthread_cond_init(&(pool->notify), NULL);
pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * thread_count);
for (int i = 0; i < thread_count; i++) {
pthread_create(&(pool->threads[i]), NULL, worker_routine, (void *)pool);
}
printf(">>> [System] Thread Pool created with %d workers.\n", thread_count);
return pool;
}
// 老板派发任务
void thread_pool_submit(ThreadPool *pool, Conn *conn, uint16_t type, void *payload, size_t payload_len) {
Task *new_task = (Task *)malloc(sizeof(Task));
new_task->conn = conn;
new_task->type = type;
new_task->next = NULL;
// 【内存安全核心】:由于主线程的 epoll 会不断覆盖 conn->in_buf
// 必须把 payload 拷贝到安全的堆内存中,让工作线程去慢慢处理
if (payload_len > 0) {
new_task->payload = malloc(payload_len);
memcpy(new_task->payload, payload, payload_len);
} else {
new_task->payload = NULL;
}
// 加锁,将任务挂到队列尾部
pthread_mutex_lock(&(pool->lock));
if (pool->tail == NULL) {
pool->head = new_task;
pool->tail = new_task;
} else {
pool->tail->next = new_task;
pool->tail = new_task;
}
// 通知(唤醒)一个正在睡觉的工作线程起来干活
pthread_cond_signal(&(pool->notify));
pthread_mutex_unlock(&(pool->lock));
}
关键点:payload 指针传给任务队列行不行?”
这就是 thread_pool_submit 函数里那个 malloc 和 memcpy 存在的意义。 如果直接传指针(指向 conn->in_buf),主线程立刻去接收下一个网络包,in_buf 里的数据就会被覆盖。等后台工作线程拿到任务准备处理时,数据早就变成了乱码(内存踩踏)。
1.3测试:
创建了包含四个线程从线程池