加入收藏 | 设为首页 | 会员中心 | 我要投稿 财气旺网 - 财气网 (https://www.caiqiwang.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

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信号量/读者写者模型/读写锁/自旋锁)
 

(编辑:财气旺网 - 财气网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!