【Linux】线程----线程池

为什么使用线程池

  • 线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度

线程池原理

  • 在服务器启动的时候,就创建大量线程 + 创建一个线程安全的队列作为线程池,任务来了,就将任务抛入线程池的任务队列中,线程池中的一个线程会循环去任务队列中获取任务进行处理

  • 线程池的实现:大量的线程 + 线程安全的任务队列

思路

  1. 通过任务类,实现向线程池抛入任务的时候,既抛入数据也抛入处理方法
  2. 线程池中的线程都只需要获取任务对象之后调用Run接口就可以实现任务处理
  3. 线程池只需要向外提供一个任务入队接口即可,任务的处理都是在线程池内部完成

代码实现

#include <cstdio>
#include <pthread.h>
#include <iostream>
#include <queue>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>

#define THREAD_COUNT 5

typedef void (*task_handler_t) (int arg);

class ThreadTask{
  private:
    int _data; // 要处理的数据
    task_handler_t _handler; // 处理数据的方法
  public:
    ThreadTask(){}
    ThreadTask(const int &data, task_handler_t handler):
    _data(data), _handler(handler){}
    void SetTask(const int &data, task_handler_t handler){
      _data = data;
      _handler = handler;
    }
    void Run(){
      return _handler(_data);
    }
};

class ThreadPool{
  private:
    int _thr_max; // 最大线程数量
    std::queue<ThreadTask> _queue;
    // 创建线程的时候,需要传入线程入口函数,导致线程池中的线程都是完成同一个任务的套路
    // 线程池中的线程不管什么数据,不管如何处理,只管调用任务对象中的Run接口
    pthread_mutex_t _mutex; // 用于实现队列操作的互斥
    pthread_cond_t _cond_pool; // 线程池中的线程都是消费者,若没有任务就阻塞
  public:
    ThreadPool(int thr_count = THREAD_COUNT):_thr_max(thr_count){
      pthread_mutex_init(&_mutex, NULL);
      pthread_cond_init(&_cond_pool, NULL);
      for(int i = 0; i < _thr_max; ++i){
        pthread_t tid;
        int ret = pthread_create(&tid, NULL, thread_routine, (void*)this);
        if(ret != 0){
          std::cerr << "thread create error!\n" << std::endl;
          exit(0);
        }
        pthread_detach(tid); // 线程分离--因为我们不关心线程的返回值
      }
    }
    ~ThreadPool(){
      pthread_mutex_destroy(&_mutex);
      pthread_cond_destroy(&_cond_pool);
    }
    bool TaskPush(const ThreadTask &task){
        pthread_mutex_lock(&_mutex);
        _queue.push(task);
        pthread_mutex_unlock(&_mutex);
        pthread_cond_signal(&_cond_pool); // 入队之后唤醒一下线程池中的线程
      return true;
    }
    void QueueLock(){
      pthread_mutex_lock(&_mutex);
    }
    bool QueueIsEmpty(){
      return _queue.empty();
    }
    void ThreadWait(){
      pthread_cond_wait(&_cond_pool, &_mutex);
    }
    bool TaskPop(ThreadTask *task){
      *task = _queue.front();
      _queue.pop();
      return true;
    }
    void QueueUnlock(){
      pthread_mutex_unlock(&_mutex);
    }
  private:
    // 这个函数如果是一个类成员函数,有一个隐藏参数this指针,因此需要定义成静态函数
    // 这是一个静态函数,没有this指针,导致无法直接访问类的内部成员变量
    // 通过参数传入当前的对象this指针,进而访问对象的公有成员变量以及成员函数
    static void *thread_routine(void *arg){
      ThreadPool *pool = (ThreadPool*)arg;
      // 循环从队列获取任务进行处理
      while(1){
        pool->QueueLock();
        while(pool->QueueIsEmpty()){
          pool->ThreadWait();
        }
        ThreadTask task;
        pool->TaskPop(&task);
        pool->QueueUnlock(); // 解锁在任务处理之前
        task.Run(); // 通过Run接口直接通过用户传入的方法完成数据处理
      }
      return NULL;
    }
};

void test(int data)
{
  srand(time(NULL));
  int sec = rand() % 5;
  printf("thread : %p : %d sleep %d sec\n", pthread_self(), data, sec);
  sleep(sec);
}

int main()
{
  ThreadPool pool;
  for(int i = 0; i < 10; i++){
    ThreadTask task;
    task.SetTask(i, test);
    pool.TaskPush(task);
  }
  while(1){
    sleep(1);
  }
  return 0;
}

注意事项

  1. 线程创建函数pthread_create要求传入的入口函数只有一个参数 void (*thread_routine)(void *arg); 但是若入口函数是一个类的成员函数,则默认会有一个隐藏参数this指针导致函数参数类型不匹配;因此需要将这个入口函数在类内定义为静态函数
  2. 静态函数无法直接访问类的成员,因此将这个对象的this指针通过线程入口函数传入,在线程内部就可以访问到这个对象了;但是类外使用无法直接访问对象的私有成员,因此对私有成员的访问都需要通过公有接口来实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值