为什么需要线程池?
1 某类任务特别耗时,严重影响该线程处理其他任务。 这类任务在其他线程异步执行
2 线程资源的开销与CPU核心之间做平衡选择(固定线程的数量,避免在任务到来的时候开启线程,任务结束的时候又销毁线程)
作用: 1复用线程资源,2充分利用系统资源。 3(异步执行耗时任务)
线程池是一个生产消费模型。
构成:
1 生产者线程 : 发布任务
2 任务队列 :
任务 , 任务的上下文,任务的执行函数。
3 线程池(消费者线程):
取出任务,执行任务,线程调度 mutex + condition(从无到有,从有到无)
4 平衡选择:
耗时任务:
IO密集型 : 2倍核心数+2 , CPU密集型: 与CPU核心数相同
确定线程池线程数量: (IO等待时间+CPU运算时间)X 核心数 / CPU运算时间
问题1:多个线程同时在一个任务队列里面取任务
任务队列就相当于这多个线程的临界资源,取任务的时候需要对任务队列进行加锁。
问题2:当任务不足的时候,线程的状态(如何休息)
条件变量(挂起) ---->进入条件等待,等待任务队列不为空的条件满足,然后唤醒线程。
实现一个线程池
1 创建线程池接口
线程的数量,队列的长度
2 销毁线程池的接口
标记线程池退出,通知所有线程
3 生产者抛出任务的接口
构造任务 ,放入任务队列 , 通知线程唤醒
网络数据处理的阶段
1. 检测IO里面是否有数据(是否可读是否可写) IO事件是否就绪 EPOLL
2. 对IO进行读写操作。 RECV \ SEND
3. 对数据进行解析。 parser解析
3种做法:
第一种: 单线程处理。
第二种: 可以把读写IO这两个任务抛到线程池里面。
第三种: recv()之后,把解析完的(buffer)动作抛到线程池里面处理。
每一种各有优点和缺点
第一种: 如果在解析的过程当中引入了数据库的操作,整个过程会很慢。如果对于任务比较轻的纯内存操作式的,这种是OK的。
第二种: 针对于(fd)IO的操作时间比较长的,(比如说客户端的每一个fd设置成阻塞的IO)就可以采用这种方式。这种做法是三种做法里面速度最快的,不影响主循环。 但是,有一个很大的问题。 多个线程共用一个fd的现象 (客户端的发送请求间隔较短,服务端分配两个线程去处理同一个fd, 可能出现线程A在准备数据或者数据就绪的时候线程B已经把这个fd关闭了,还有可能AB两个线程同时发送,就会出现一些脏数据的现象。
这三个步骤都可以分多线程进行。
任务队列是两个东西,首先是任务,其次才是队列。 任务先描述清楚,这个队列就是一个集合。
怎么把任务一个一个封装比较好 (写日记,计算,crud)。 首先每一个任务都不一样,这里我们需要一个回调函数,这个回调函数是由任务自己本身去实现的。 第二还有一个参数,
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #define LL_ADD(item, list) do { \ item->prev = NULL; \ item->next = list; \ list = item; \ } while(0) #define LL_REMOVE(item, list) do { \ if (item->prev != NULL) item->prev->next = item->next; \ if (item->next != NULL) item->next->prev = item->prev; \ if (list == item) list = item->next; \ item->prev = item->next = NULL; \ } while(0) typedef struct NWORKER { pthread_t thread; int terminate; struct NWORKQUEUE *workqueue; struct NWORKER *prev; struct NWORKER *next; } nWorker; typedef struct NJOB { void (*job_function)(struct NJOB *job); void *user_data; struct NJOB *prev; struct NJOB *next; } nJob; typedef struct NWORKQUEUE { struct NWORKER *workers; struct NJOB *waiting_jobs; pthread_mutex_t jobs_mtx; pthread_cond_t jobs_cond; } nWorkQueue; typedef nWorkQueue nThreadPool; static void *ntyWorkerThread(void *ptr) { nWorker *worker = (nWorker*)ptr; while (1) { pthread_mutex_lock(&worker->workqueue->jobs_mtx); while (worker->workqueue->waiting_jobs == NULL) { if (worker->terminate) break; pthread_cond_wait(&worker->workqueue->jobs_cond, &worker->workqueue->jobs_mtx); } if (worker->terminate) { pthread_mutex_unlock(&worker->workqueue->jobs_mtx); break; } nJob *job = worker->workqueue->waiting_jobs; if (job != NULL) { LL_REMOVE(job, worker->workqueue->waiting_jobs); } pthread_mutex_unlock(&worker->workqueue->jobs_mtx); if (job == NULL) continue; job->job_function(job); } free(worker); pthread_exit(NULL); } int ntyThreadPoolCreate(nThreadPool *workqueue, int numWorkers) { if (numWorkers < 1) numWorkers = 1; memset(workqueue, 0, sizeof(nThreadPool)); pthread_cond_t blank_cond = PTHREAD_COND_INITIALIZER; memcpy(&workqueue->jobs_cond, &blank_cond, sizeof(workqueue->jobs_cond)); pthread_mutex_t blank_mutex = PTHREAD_MUTEX_INITIALIZER; memcpy(&workqueue->jobs_mtx, &blank_mutex, sizeof(workqueue->jobs_mtx)); int i = 0; for (i = 0;i < numWorkers;i ++) { nWorker *worker = (nWorker*)malloc(sizeof(nWorker)); if (worker == NULL) { perror("malloc"); return 1; } memset(worker, 0, sizeof(nWorker)); worker->workqueue = workqueue; int ret = pthread_create(&worker->thread, NULL, ntyWorkerThread, (void *)worker); if (ret) { perror("pthread_create"); free(worker); return 1; } LL_ADD(worker, worker->workqueue->workers); } return 0; } void ntyThreadPoolShutdown(nThreadPool *workqueue) { nWorker *worker = NULL; for (worker = workqueue->workers;worker != NULL;worker = worker->next) { worker->terminate = 1; } pthread_mutex_lock(&workqueue->jobs_mtx); workqueue->workers = NULL; workqueue->waiting_jobs = NULL; pthread_cond_broadcast(&workqueue->jobs_cond); pthread_mutex_unlock(&workqueue->jobs_mtx); } void ntyThreadPoolQueue(nThreadPool *workqueue, nJob *job) { pthread_mutex_lock(&workqueue->jobs_mtx); LL_ADD(job, workqueue->waiting_jobs); pthread_cond_signal(&workqueue->jobs_cond); pthread_mutex_unlock(&workqueue->jobs_mtx); } /************************** debug thread pool **************************/ //sdk --> software develop kit // 提供SDK给其他开发者使用 #if 1 #define KING_MAX_THREAD 80 #define KING_COUNTER_SIZE 1000 void king_counter(nJob *job) { int index = *(int*)job->user_data; printf("index : %d, selfid : %lu\n", index, pthread_self()); free(job->user_data); free(job); } int main(int argc, char *argv[]) { nThreadPool pool; ntyThreadPoolCreate(&pool, KING_MAX_THREAD); int i = 0; for (i = 0;i < KING_COUNTER_SIZE;i ++) { nJob *job = (nJob*)malloc(sizeof(nJob)); if (job == NULL) { perror("malloc"); exit(1); } job->job_function = king_counter; job->user_data = malloc(sizeof(int)); *(int*)job->user_data = i; ntyThreadPoolQueue(&pool, job); } getchar(); printf("\n"); } #endif
1. 多线程共用fd,如何避免多线程使用的脏数据?
2. 线程池的放缩策略,如何做比较好?
nginx 中线程池
作用:处理文件缓冲;
开启线程池
# 线程池默认关闭,configure 时,需要 --with-threads 来指定;
./configure --with-pcre-jit --with-http_ssl_module --with-http_realip_module --with-http_stub_status_module --with-http_v2_module -- with-threads
# 解决 gdb 远程权限的问题
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
# 创建名为 mark 的线程池
thread_pool mark threads=32 max_queue=65535;
location / {
root /img; aio threads=mark;
}
location / {
sendfile on; sendfile_max_chunk 128k; # 默认是没有限制的
}
location / {
directio 8m;
}
redis 中线程池
作用:读写 io 处理以及数据包解析、压缩;
redis当中的线程池,主线程既是生产者,也是消费者。且线程池的设计没有使用条件变量。主线程持有子线程的互斥锁,让子线程进入睡眠状态。
skynet 中线程池
作用:处理读写 io 、数据包解压缩、业务逻辑处理;特别地: 当同一个 io 在多个线程处理时,将写 io 转由网络线程处理;
线程池作用阶段
reactor将消息事件放入全局队列,线程池去全局队列消费。