Linux多线程:线程池

简介: Linux多线程:线程池

一、线程池简介


线程池:由一堆工作线程+一个线程安全的任务队列构成。


       外界将需要处理的任务,加入到线程安全的任务队列中,线程池中的工作线程不断的从任务队列中取出任务进行处理。


二、应用场景


应用场景:有大量数据请求,需要并发处理的场景。


       1)需要大量的线程来完成任务,且完成任务的时间较短;


       2)对性能要求苛刻的应用,比如要求服务器迅速响应客户请求;


       3)接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。


大量请求需要处理,若针对每一个请求创建一个线程,会存在以下问题:


       1)资源耗尽,系统崩溃风险


       2)在任务的并发处理中,执行流并不是越多越好


注:一个任务处理的总时间消耗 = 创建线程的时间 + 任务处理的时间 + 线程销毁的时间,则在任务处理中,大量的时间成本消耗在了线程的创建与销毁上。此时就可以选择应用线程池来进行任务处理。


三、线程池的实现


1.创建一堆线程


2.创建一个线程安全的任务队列


3.定义任务处理方法


       不同的客户端可能有不同的请求,不同的请求右不同的处理方式。


解决方案:


       让外界在传入请求的同时,附上此请求的对应处理方法,线程只需要根据请求和传入的处理方法去进行任务处理即可。


4.任务处理


四、代码实现


1.完整代码


#include<iostream>
#include<pthread.h>
#include<queue>
#include<cstdlib>
#include<unistd.h>
#define MAX_QUEUE 5
#define MAX_THREAD 5
//线程安全的任务队列
template <class T>
class BlockQueue{
  private:
    int _capacity;//缓冲区容量
    std::queue<T> _queue;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond_pro;
    pthread_cond_t _cond_con;
  public:
    BlockQueue(int cap = MAX_QUEUE) : _capacity(cap) {
      pthread_mutex_init(&_mutex, NULL);
      pthread_cond_init(&_cond_pro, NULL);
      pthread_cond_init(&_cond_con, NULL);
    }
    ~BlockQueue() {
      pthread_mutex_destroy(&_mutex);
      pthread_cond_destroy(&_cond_pro);
      pthread_cond_destroy(&_cond_con);
    }
    bool Push(const T &data) {
      pthread_mutex_lock(&_mutex);
      while (_queue.size() == _capacity) {
        pthread_cond_wait(&_cond_pro, &_mutex);
      }
      _queue.push(data);
      pthread_cond_signal(&_cond_con);
      pthread_mutex_unlock(&_mutex);
    }
    bool Pop(T *data) {
      pthread_mutex_lock(&_mutex);
      while (_queue.empty()) {
        pthread_cond_wait(&_cond_con, &_mutex);
      }
      *data = _queue.front();
      _queue.pop();
      pthread_cond_signal(&_cond_pro);
      pthread_mutex_unlock(&_mutex);
    }
};
//任务处理方法
typedef void (*handler_t)(int data);
class ThreadTask {
  private:
    int _data;
    handler_t _handler;
  public:
    ThreadTask() {}
    ThreadTask(int data, handler_t handler)
      :_data(data)
       ,_handler(handler)
  {}
    void Run() {
      _handler(_data);
    }
};
//线程池
class ThreadPool {
  private:
    int thread_count;
    BlockQueue<ThreadTask> _queue;
    //这里需要设置为静态成员函数,否则参数多一个this指针,无法匹配
    static void *Worker(void *arg) {
      //不断取出任务并处理
      ThreadPool *pool = (ThreadPool*)arg;
      while (1) {
        ThreadTask task;
        pool->_queue.Pop(&task);
        task.Run();
      }
    }
  public:
    ThreadPool(int tcount = MAX_THREAD, int qcount = MAX_QUEUE)
      :thread_count(tcount)
       ,_queue(qcount)
  {
    int ret;
    pthread_t tid;
    //创建工作线程
    for (int i = 0; i < tcount; ++i) {
      ret = pthread_create(&tid, NULL, Worker, this);
      if (ret != 0) {
        printf("Create thread error!\n");
        exit(0);
      }
      pthread_detach(tid);//将线程分离,不关心其退出
    }
  }
    bool Push(const ThreadTask &task) {
      _queue.Push(task);
    }
};
//处理方法
void Conduct(int data) {
  printf("Thread: %p --- sleep %d seconds!\n", pthread_self(), data);
  sleep(data % 4 + 1);
}
int main() {
  ThreadPool pool;
  for (int i = 0; i < 20; ++i) {
    ThreadTask task(i, Conduct);
    pool.Push(task);
  }
  while (1) sleep(1);//防止程序退出
  return 0;
}


2.实现效果

1.png


3.注意事项


1)线程入口函数类型不匹配:


       若入口函数是一个类的成员函数,那么参数列表会有一个隐藏的this指针,导致入口函数类型不匹配,


解决方法:


       可将线程的入口函数设置为静态成员函数(静态成员函数,不含this指针)。


2)静态成员函数无法直接访问类的普通成员变量:


       静态成员函数在调用时,因为没有传入this指针,无法直接访问类的普通成员变量。


有问题的解决方案:


       将入口函数中访问类的成员变量(阻塞队列),设置为静态。


       存在问题:若将类的成员变量(阻塞队列)设置为静态,则类实例化的所有对象共用同一个静态成员变量(阻塞队列),所以不可取。


合适的解决方案:


       在创建线程池时,将线程池对象的this指针传入到线程的入口函数中,通过this指针,来访问其对应的成员变量(阻塞队列)。


相关文章
|
4天前
|
Python
|
2天前
|
NoSQL Redis 缓存
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?
【5月更文挑战第17天】Redis常被称为单线程,但实际上其在处理命令时采用单线程,但在6.0后IO变为多线程。持久化和数据同步等任务由额外线程处理,因此严格来说Redis是多线程的。面试时需理解Redis的IO模型,如epoll和Reactor模式,以及其内存操作带来的高性能。Redis使用epoll进行高效文件描述符管理,实现高性能的网络IO。在讨论Redis与Memcached的线程模型差异时,应强调Redis的单线程模型如何通过内存操作和高效IO实现高性能。
28 7
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?
|
5天前
|
监控 Java 测试技术
在多线程开发中,线程死循环可能导致系统资源耗尽,影响应用性能和稳定性
【5月更文挑战第16天】在多线程开发中,线程死循环可能导致系统资源耗尽,影响应用性能和稳定性。为解决这一问题,建议通过日志记录、线程监控工具和堆栈跟踪来定位死循环;处理时,及时终止线程、清理资源并添加错误处理机制;编码阶段要避免无限循环,正确使用同步互斥,进行代码审查和测试,以降低风险。
18 3
|
6天前
|
Linux C语言 调度
|
6天前
|
Unix Linux 调度
linux线程与进程的区别及线程的优势
linux线程与进程的区别及线程的优势
|
6天前
|
存储 算法 Linux
【Linux】线程的内核级理解&&详谈页表以及虚拟地址到物理地址之间的转化
【Linux】线程的内核级理解&&详谈页表以及虚拟地址到物理地址之间的转化
|
6天前
|
算法 安全 Linux
【探索Linux】P.20(多线程 | 线程互斥 | 互斥锁 | 死锁 | 资源饥饿)
【探索Linux】P.20(多线程 | 线程互斥 | 互斥锁 | 死锁 | 资源饥饿)
13 0
|
6天前
|
安全 Linux 调度
【linux线程(二)】线程互斥与线程同步
【linux线程(二)】线程互斥与线程同步