Linux 多线程(线程池/线程安全的单例模式/STL容器线程安全问题/智能指针
发布时间:2022-09-28 14:57:29 所属栏目:Linux 来源:
导读: 与之前的生产者消费者模型, 读者写者模型一样, 线程池也是为了解决某些典型场景下的问题而出现的.
举个例子, 当我们双十一疯狂剁手的时候, 假设我们用户的每一笔订单请求在服务器端都要创建一个线程的话
举个例子, 当我们双十一疯狂剁手的时候, 假设我们用户的每一笔订单请求在服务器端都要创建一个线程的话
与之前的生产者消费者模型, 读者写者模型一样, 线程池也是为了解决某些典型场景下的问题而出现的. 举个例子, 当我们双十一疯狂剁手的时候, 假设我们用户的每一笔订单请求在服务器端都要创建一个线程的话, 当11.11号0点 来临, 每一年我们都在刷新着记录, 所以在短时间内会有数量非常非常大的各种任务请求, 如果服务器端要在这些请求到来时 在一个一个的创建线程的话, 需要非常大的时空开销.其实, 这些线程也不一定要等待大量任务请来的时候才一个个创建, 我 们可以提前创建好解决各种任务的线程, 把他们放在"线程池"中, 当有任务请求来时, 直接将请求给"线程池", "线程池"中的线程 就可以直接完成这个任务请求. 免去了请求高峰时段频繁创建线程而带来的花销. 解决的问题 : 短时间大量任务请求下创建大量线程带来的时空开销, 调度开销, 以及有资源耗尽的风险 优点 : 避免峰值压力下, 资源耗尽的风险. 节省线程创建销毁的时间成本 实现 : 一堆线程(有最大数量限制) + 一个线程安全的任务队列 具体思路 : 思路简单来说, 就是一个ThreadPool的对象中创建许多个线程, 当这个对象的任务等待队列中有任务请求, 也就是push操作时, 唤醒所创建的线程去完成这个任务, 完成任务后, 这个任务就可以pop出任务等待队列. 那么请求任务也不一定是一样的啊, 就比如淘宝来说, 有买的请求, 也有退货的等等, 所以, 我们还需将任务处理的方法与数据封装为一个类ThreadTask, 在线程入口函数中, 只需要调用我们封装的这个类的接口, 不需要关心方法与数据是什么, 保证了我们线程库的实现中, 线程不需要关心要处理什么具体问题, 只需要调用一个固定的接口就可以. 大大降低了耦合性, 我们有不同的任务请求时, 只需要实例化出不同的ThreadTask对象就可以了. 我们来封装一个简单的线程池 ThreadPool.hpp #ifndef __SOMEFILE_H__ #define __SOMEFILE_H__ #include #include #include #include #include #include using namespace std; #define MAX_COUNT 10 typedef void (*task_handler_t)(int); class ThreadTask{ public: int m_data; task_handler_t m_handler; public: ThreadTask(int data, task_handler_t handler) : m_data(data), m_handler(handler) {} void Run(){ m_handler(m_data); } }; class ThreadPool{ size_t m_max_count;//定义线程池中线程的数量 queue m_task_q;//任务队列 pthread_mutex_t m_mutex;//互斥锁 pthread_cond_t m_pro_cond, m_con_cond;//条件变量 bool m_exit_flag;//析构标记 vector m_tid_array;//线程池中所有线程的tid size_t m_destroy_count;//销毁线程计数 static void* thr_start(void* arg); void Quit(); public: ThreadPool(size_t max_count = MAX_COUNT); ~ThreadPool(); bool PushTask(const ThreadTask& task); }; ThreadPool::ThreadPool(size_t max_count) : m_max_count(max_count), m_exit_flag(false), m_destroy_count(0), m_tid_array(max_count) { pthread_mutex_init(&m_mutex, NULL); pthread_cond_init(&m_con_cond, NULL); pthread_cond_init(&m_pro_cond, NULL); int ret; for(size_t i = 0; i exit(0); } //若不关心线程返回值, 并希望线程退出后能够自己释放资源 //pthread_detach(m_tid_array[i]);//则分离这个线程 //否则需要记录所创建所有线程的tid, 再析构时对所有线程pthread_join } } ThreadPool::~ThreadPool(){ Quit(); for(size_t i = 0; i < m_max_count; pthread_join(m_tid_array[i], NULL), ++i); //detach属性则不需要等待 pthread_mutex_destroy(&m_mutex); pthread_cond_destroy(&m_pro_cond); pthread_cond_destroy(&m_con_cond); } void* ThreadPool::thr_start(void* arg){ ThreadPool* tp = (ThreadPool*)arg; while(1){ pthread_mutex_lock(&(tp->m_mutex)); while(tp->m_task_q.empty()){ if(tp->m_exit_flag) { ++(tp->m_destroy_count); pthread_mutex_unlock(&(tp->m_mutex)); cout<<"线程退出\n";//用于测试 pthread_exit(NULL); } pthread_cond_wait(&(tp->m_con_cond), &(tp->m_mutex)); } ThreadTask task = tp->m_task_q.front(); tp->m_task_q.pop(); pthread_mutex_unlock(&(tp->m_mutex)); task.Run();//要解锁之后再进行任务处理 pthread_cond_signal(&(tp->m_pro_cond)); } pthread_exit(NULL); } bool ThreadPool::PushTask(const ThreadTask& task){ pthread_mutex_lock(&m_mutex); m_task_q.push(task); pthread_mutex_unlock(&m_mutex); pthread_cond_signal(&m_con_cond); return true; } void ThreadPool::Quit(){ pthread_mutex_lock(&m_mutex); m_exit_flag = true; pthread_mutex_unlock(&m_mutex); while(m_destroy_count != m_max_count) { pthread_cond_broadcast(&m_con_cond); } } #endif main.cpp #include #include #include #include"thread_pool.hpp" void test(int data){ printf("thread:%p get data:%d\n", pthread_self(), data); } int main(int argc, char* argv[]){ if(argc != 2){ return -1; } ThreadPool pool(4); for(int i = 0; i } return 0; } linux线程池_线程池linux_线程池 linux 线程安全的单例模式 单例模式,属于创建类型的一种常用的设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例线程池linux,如:仅线程上下文内使用同一个实例) 设计模式 :针对一些经典的常见的场景, 制定的一些对应的解决方案, 就是设计模式. 单例模式的特点 某些类, 只应该具有一个对象(实例), 就称之为单例. 例如 : 一个男人只能有一个媳妇. 针对场景 :在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据(也就是一份资源只加载一次). 饿汉实现方式和懒汉实现方式 什么是饿汉和饱汉呢 ? 举个例子 吃完饭之后, 马上去洗碗, 下回吃饭之前就不需要洗完了, 这就是饿汉,即在程序初始化阶段就去加载资源, 后面程序运行只需要使用.优点 : 运行流畅 缺点 : 初始加载较慢 吃完饭之后, 先不去洗碗, 等到下回吃饭之前再洗, 这就是懒汉, 即程序只有在使用某些资源时才会去加载, 优点 : 初始加载快 缺点 : 运行时可能会会加载资源, 可能会不够流畅 懒汉模式的核心是"延时加载", 从而优化程序的启动速度 饿汉模式实现单例模式 using namespace std; template class singleton{ static T m_data; public: T* gey_instance(){ return &m_data; } }; template T singleton::m_data = 0; 懒汉模式实现单例模式 template class singleton{ volatile static T* m_data; static mutex m_mutex; public: volatile T* get_instance(){ if(m_data == nullptr){ m_mutex.lock(); if(m_data == nullptr){ m_data = new int; } m_mutex.unlock(); } return m_data; } ~singleton(){ if(m_data){ delete m_data; m_data = nullptr; } } }; template volatile T* singleton::m_data = nullptr; 需要注意的是, 懒汉模式存在线程安全的问题, 所以需要实现线程安全. 注意事项: STL线程安全的问题 STL在设计之初, 就是为了极致的性能, 而锁对极致性能来说是无情的杀手, 所以就没有实现线程安全. 所以使用STL中的容器 等存在线程安全问题, 需要我们对具体问题, 具体设计, 比如上面的线程池中任务队列的push和pop就需要我们手动实现线程 安全. 智能指针线程安全问题 对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题. 对于 shared_ptr, 多个对象需要共用一个引用计数变量(shared_ptr内部有引用计数), 所以会存在线程安全问题. 但是标准库 实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数. 所以说智能指针 是线程安全的. 智能指针相关博客,戳链接( ̄︶ ̄)↗C++ 智能指针(auto_ptr/unique_ptr/shared_ptr) 相关博客,戳链接( ̄︶ ̄)↗ :Linux 多线程(线程概念/特点/优缺点/与进程比较) Linux 多线程(线程控制(创建/终止/等待/分离)) Linux 多线程之线程安全(同步与互斥/互斥锁/条件变量/死锁/) Linux 多线程之线程安全(生产者消费者模型/POSIX信号量/读者写者模型/读写锁/自旋锁) (编辑:财气旺网 - 财气网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
站长推荐