线程相关问题(webserver项目)
线程池相关
手写线程池
线程池有三个类:
- 任务类
- 任务队列
- 线程池
主要组成部分是三个:
-
任务队列,存储需要分配的任务,用队列实现
-
工作线程。
工作线程不停地读取任务队列,读取里面的任务并处理。
如果队列为空,工作线程将会被阻塞,条件变量
队列不为空,唤醒线程,工作
-
管理线程。
周期性的对任务队列中的任务数量以及处于忙碌状态的工作线程个数进行检测,当任务过多就适当创建一些新线程,当任务过少就销毁一些线程。
#include<pthread.h> #include<queue> #include<iostream> #include<string.h> #include<exception> #include<unistd.h> const int NUMBER = 2; using callback = void(*)(void* arg); class Task{ private: public: callback fun; void* arg; public: Task(){ fun = nullptr; arg = nullptr; } Task(callback f, void* arg){ fun = f; this->arg = arg; } }; class Task_queue { private: pthread_mutex_t m_mutex; std::queue<Task> m_queue; public: Task_queue(); ~Task_queue(); //添加任务 void add_task(Task& task); void add_task(callback f, void* arg); //取出任务 Task& get_task(); int task_size(){ return m_queue.size(); } }; class Thread_pool{ private: pthread_mutex_t m_mutex_pool; pthread_cond_t m_not_empty; //条件变量,用来唤醒等待的线程,假设队列是无限的 pthread_t* thread_compose; pthread_t m_manger_thread; Task_queue* m_task_queue; int m_min_num; //最小线程数 int m_max_num; //最大线程数 int m_busy_num; //工作的线程数 int m_live_num; //存活的线程数一 int m_destory_num; //待销毁的线程数 bool shutdown = false; //是否销毁 //在使用pthread_create时候,第三个参数的函数必须是静态的 //要在静态函数中使用类的成员(成员函数和变量)只能通过两种方式 //1.通过类的静态对象,单例模式中使用类的全局唯一实例来访问类成员 //2.类对象作为参数传递给该静态函数中 //管理线程的任务函数,形参是线程池类型的参数 static void* manager(void* arg); //工作线程的任务函数 static void* worker(void* arg); //销毁线程池 void destory_thread(); public: Thread_pool(int min, int max); ~Thread_pool(); void add_task(Task task); int get_busy_num(); int get_live_num(); }; Task_queue::Task_queue() { pthread_mutex_init(&m_mutex, nullptr); } Task_queue::~Task_queue() { pthread_mutex_destroy(&m_mutex); } void Task_queue::add_task(Task& task){ pthread_mutex_lock(&m_mutex); m_queue.push(task); pthread_mutex_unlock(&m_mutex); } void Task_queue::add_task(callback f, void* arg){ pthread_mutex_lock(&m_mutex); Task t(f, arg); m_queue.push(t); pthread_mutex_unlock(&m_mutex); } Task& Task_queue::get_task(){ Task t; pthread_mutex_lock(&m_mutex); if(m_queue.size() > 0){ t = m_queue.front(); m_queue.pop(); } pthread_mutex_unlock(&m_mutex); return t; } Thread_pool::Thread_pool(int min, int max){ //实例化任务队列 m_task_queue = new Task_queue; //do while的好处,但是c++有析构,所以不太用的上 do { //初始化线程池 m_min_num = min; m_max_num = max; m_busy_num = 0; m_live_num = min; //根据最大上限给线程数组分配内存 thread_compose = new pthread_t[m_max_num]; if(thread_compose == nullptr){ std::cout<<"create thread array fail~\n"; break; } //初始化线程数组 memset(&thread_compose, 0, sizeof(thread_compose) * m_max_num); //初始化互斥锁和条件变量 if(pthread_mutex_init(&m_mutex_pool, nullptr) != 0){ throw::std::exception(); } if(pthread_cond_init(&m_not_empty, nullptr) != 0){ throw::std::exception(); } //创建管理者线程 pthread_create(&m_manger_thread, nullptr, manager, this); //创建工作线程 for(int i = 0; i < m_min_num; i++){ //传this指针,才能访问类内成员函数 pthread_create(&thread_compose[i], nullptr, worker, this); } } while (0); } Thread_pool::~Thread_pool(){ shutdown = 1; //销毁管理者线程 pthread_join(m_manger_thread, NULL); //唤醒所有消费者线程 for (int i = 0; i < m_live_num; ++i) { //signal是唤醒某一个,broadcast是唤醒所有,但是还是要抢一把锁,所以都一样 pthread_cond_signal(&m_not_empty); } if(m_task_queue){ delete m_task_queue; } if(thread_compose){ delete [] thread_compose; } pthread_mutex_destroy(&m_mutex_pool); pthread_cond_destroy(&m_not_empty); } void Thread_pool::add_task(Task task){ if(shutdown) { std::cout<<"the thread pool will destory!\n"; return; } //添加任务不需要加锁,因为任务队列中有锁 m_task_queue->add_task(task); //添加完任务就要唤醒工作线程取任务 pthread_cond_signal(&m_not_empty); } int Thread_pool::get_busy_num(){ int busy_num = 0; pthread_mutex_lock(&m_mutex_pool); busy_num = m_busy_num; pthread_mutex_unlock(&m_mutex_pool); return busy_num; } int Thread_pool::get_live_num(){ int live_num = 0; pthread_mutex_lock(&m_mutex_pool); live_num = m_live_num; pthread_mutex_unlock(&m_mutex_pool); return live_num; } //管理线程任务函数 //主要任务:不断检测工作线程数量,存活线程的数量,然后再决定增加还是删除 void* Thread_pool::manager(void* arg){ Thread_pool* pool = static_cast<Thread_pool*>(arg); while(pool->shutdown){ //每五秒检测一次 sleep(5); //取出线程数量 pthread_mutex_lock(&pool->m_mutex_pool); int queue_size = pool->m_task_queue->task_size(); int live_size = pool->m_live_num; int busy_size = pool->m_busy_num; pthread_mutex_unlock(&pool->m_mutex_pool); //创建线程,最多创建两个 if(queue_size > live_size && live_size <pool->m_max_num){ pthread_mutex_lock(&pool->m_mutex_pool); int count = 0; for(int i = 0; i < pool->m_max_num && count < NUMBER && pool->m_live_num < pool->m_max_num; i++){ //之前memset了 if(pool->thread_compose[i] == 0){ pthread_create(&pool->thread_compose[i], nullptr, worker, pool); count++; pool->m_live_num++; } } pthread_mutex_unlock(&pool->m_mutex_pool); } //销毁多余的线程 //判断条件:忙线程*2 < 存活的线程 && 存活的线程数 > 最小线程数 if(2*busy_size < live_size && live_size > pool->m_min_num){ pthread_mutex_lock(&pool->m_mutex_pool); pool->m_destory_num = NUMBER; pthread_mutex_unlock(&pool->m_mutex_pool); //让工作线程自杀——唤醒但没事儿干就自杀,定义在worker里面 //没事儿干的线程被阻塞了,阻塞在m_not_empty()条件变量上 //唤醒后的线程就自己退出了,代码在worker里面 for (int i = 0; i < NUMBER; ++i) { pthread_cond_signal(&pool->m_not_empty); } } } return nullptr; } //工作线程任务函数 void* Thread_pool::worker(void* arg){ Thread_pool* pool = static_cast<Thread_pool*>(arg); //工作线程一直不停工作 while(true){ //当线程访问任务队列的时候加锁 pthread_mutex_lock(&pool->m_mutex_pool); //判断工作队列是否为空,为空阻塞线程 while(pool->m_task_queue->task_size() == 0 && pool->shutdown == 1){ std::cout<<"thread "<<std::to_string(pthread_self())<<"waiting...\n"; //将调用线程放入条件变量的等待队列中 pthread_cond_wait(&pool->m_not_empty, &pool->m_mutex_pool); //解除阻塞之后判断是否要销毁线程 //由于管理线程中满足线程销毁条件了,就通过条件变量唤醒线程 //然后由于唤醒的线程是空闲的即任务队列中没东西,如果有东西就不会空闲, //不会满足manager线程中销毁线程的条件 if(pool->m_destory_num > 0){ pool->m_destory_num--; if(pool->m_live_num > pool->m_min_num){ pool->m_live_num--; //先解锁再销毁 pthread_mutex_unlock(&pool->m_mutex_pool); pool->destory_thread(); } } } if(pool->shutdown){ pthread_mutex_unlock(&pool->m_mutex_pool); pool->destory_thread(); } //取任务 Task task = pool->m_task_queue->get_task(); pool->m_busy_num++; pthread_mutex_unlock(&pool->m_mutex_pool); //执行任务 std::cout<<"thread "<<std::to_string(pthread_self())<<" start working.....\n"; task.fun(task.arg); delete task.arg; task.arg = nullptr; //任务处理结束 std::cout<<"thread "<<std::to_string(pthread_self())<<" end work.....\n"; pthread_mutex_lock(&pool->m_mutex_pool); pool->m_busy_num--; pthread_mutex_unlock(&pool->m_mutex_pool); } return nullptr; } void Thread_pool::destory_thread(){ pthread_t tid = pthread_self(); for(int i = 0; i < m_max_num; i++){ if(thread_compose[i] == tid){ std::cout << "threadExit() function: thread " << std::to_string(pthread_self()) << " exiting..." << std::endl; } thread_compose[i] = 0; break; } pthread_exit(nullptr); }
线程的同步机制有哪些?
临界区:临界区指的是一个访问共用资源(例如:共用设备或是共用存储器)的程序片段,而这些共用资源又无法同时被多个线程访问的特性。当有线程进入临界区段时,其他线程或是进程必须等待(例如:bounded waiting 等待法),有一些同步的机制必须在临界区段的进入点与离开点实现,以确保这些共用资源是被互斥获得使用。通过对多线程的串行化来访问公共资源或者一段代码,速度快,适合控制数据访问。
互斥量:为协调对一个共享资源的单独访问而设计,只有拥有互斥量的线程才有权限访问系统的公共资源,因为互斥量只有一个,所以能保证资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源的访问,还能实现不同应用程序公共资源的安全共享。
信号量:为控制一个具有有限数量的用户资源而设计。它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目。
事件:用来通知线程有一些事件已经发生,从而启动后续任务的开始。
项目中使用队列和锁实现。多个线程访问同一个对象,并且某些线程还想修改这个对象,这时需要线程同步。
线程池中的工作线程是一直等待吗?
为了能够处理高并发的问题,工作线程设置为阻塞等待在请求队列是否不为空的条件上,线程池中的工作线程是处于一直阻塞等待的模式下的。在创建线程池时,通过循环调用pthread_create往线程池中创建了8个工作线程,工作线程处理函数接口为pthread_create函数原型中第三个参数函数指针所指向的worker函数(自定义的函数),然后调用线程池类成员函数run(自定义)。
你的线程池工作线程处理完一个任务后的状态是什么?
分两种情况考虑
(1) 当处理完任务后如果请求队列为空时,这个线程重新回到阻塞等待的状态。
(2) 当处理完任务后如果请求队列不为空时,这个线程将处于与其他线程竞争资源的状态,谁获得锁谁就获得了处理事件的资格。
如果同时1000个客户端进行访问请求,线程数不多,怎么能及时响应处理每一个呢?
one loop pre thread
首先这种问法就相当于问服务器如何处理高并发的问题,本项目中是通过对子线程循环调用来解决高并发的问题的。
具体实现过程如下:
在创建线程的同时调用pthread_detach将线程进行分离,这样就不用单独对工作线程进行回收,但是一般情况只要设置了分离属性,这个线程在处理完任务后,也就是子线程结束后,资源会被自动回收。这种情况下服务器基本就只能处理8个请求事件了(线程池里只有8个线程)。那怎么实现高并发的请求呢?可能会说让线程池里创建足够多的线程数,这当然是理想化的,现实中线程数量过大会导致更多的线程上下文切换,占用更多内存,这显然是不合理的。
接下来所叙述的就是本项目中用来处理高并发问题的方法了:
我们知道调用了pthread_detach的线程只有等到他结束时系统才会回收他的资源,那么我们就可以从这里下手了。我们通过子线程的run调用函数进行while循环,让每一个线程池中的线程永远都不会终止,说白了就是让他处理完当前任务就去处理下一个,没有任务就一直阻塞在那里等待。这样就能达到服务器高并发的要求,同一时刻8个线程都在处理请求任务,处理完之后接着处理,直到请求队列为空表示任务全部处理完成。
如果一个客户请求需要占用线程很久的时间,会不会影响接下来的客户请求呢,有什么好的策略呢?
会影响接下来的客户请求,因为线程池内线程的数量时有限的,如果客户请求占用线程时间过久的话会影响到处理请求的效率,当请求处理过慢时会造成后续接受的请求只能在请求队列中等待被处理,从而影响接下来的客户请求。
应对策略(定时器):
可以为线程处理请求对象设置处理超时时间, 超过时间先发送信号告知线程处理超时,然后设定一个时间间隔再次检测,若此时这个请求还占用线程则直接将其断开连接。
如果线程过多,内存较小,有什么优化方法?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现