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指针,来访问其对应的成员变量(阻塞队列)。


相关文章
|
21天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
100 38
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
43 1
C++ 多线程之初识多线程
|
19天前
|
Java
线程池内部机制:线程的保活与回收策略
【10月更文挑战第24天】 线程池是现代并发编程中管理线程资源的一种高效机制。它不仅能够复用线程,减少创建和销毁线程的开销,还能有效控制并发线程的数量,提高系统资源的利用率。本文将深入探讨线程池中线程的保活和回收机制,帮助你更好地理解和使用线程池。
44 2
|
21天前
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
58 4
|
21天前
|
Prometheus 监控 Cloud Native
在 Java 中,如何使用线程池监控以及动态调整线程池?
【10月更文挑战第22天】线程池的监控和动态调整是一项重要的任务,需要我们结合具体的应用场景和需求,选择合适的方法和策略,以确保线程池始终处于最优状态,提高系统的性能和稳定性。
89 2
|
23天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
17 3
|
23天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
16 2
|
23天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
28 2
|
23天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
28 1
|
23天前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
34 1