zoukankan      html  css  js  c++  java
  • C++11多线程编程(六)——线程池的实现

    学一门新技术,还是要问那个问题,为什么我们需要这个技术,这个技术能解决什么痛点。

    一、为何需要线程池

    那么为什么我们需要线程池技术呢?多线程编程用的好好的,干嘛还要引入线程池这个东西呢?引入一个新的技术肯定不是为了装逼,肯定是为了解决某个问题的,而服务端一般都是效率问题。

    我们可以看到多线程提高了CPU的使用率和程序的工作效率,但是如果有大量的线程,就会影响性能,因为要大量的创建与销毁,因为CPU需要在它们之间切换。线程池可以想象成一个池子,它的作用就是让每一个线程结束后,并不会销毁,而是放回到线程池中成为空闲状态,等待下一个对象来使用。

    二、C++中的线程池

    但是让人遗憾的是,C++并没有在语言级别上支持线程池技术,总感觉C++委员会对多线程的支持像是犹抱琵琶半遮面的羞羞女一样,无法完全的放开。

    虽然无法从语言级别上支持,但是我们可以利用条件变量和互斥锁自己实现一个线程池。这里就不得不啰嗦几句,条件变量和互斥锁就像两把利剑,几乎可以实现多线程技术中的大部分问题,不管是生产消费者模型,还是线程池,亦或是信号量,所以我们必须好好掌握好这两个工具。

     1 #ifndef _THREADPOOL_H
     2 #define _THREADPOOL_H
     3 #include <vector>
     4 #include <queue>
     5 #include <thread>
     6 #include <iostream>
     7 #include <condition_variable>
     8 using namespace std;
     9  
    10 const int MAX_THREADS = 1000; //最大线程数目
    11  
    12 template <typename T>
    13 class threadPool
    14 {
    15 public:
    16     threadPool(int number = 1);
    17     ~threadPool();
    18     bool append(T *task);
    19     //工作线程需要运行的函数,不断的从任务队列中取出并执行
    20     static void *worker(void *arg);
    21     void run();
    22  
    23 private:
    24     //工作线程
    25     vector<thread> workThread;
    26     //任务队列
    27     queue<T *> taskQueue;
    28     mutex mt;
    29     condition_variable condition;
    30     bool stop;
    31 };
    32  
    33 template <typename T>
    34 threadPool<T>::threadPool(int number) : stop(false)
    35 {
    36     if (number <= 0 || number > MAX_THREADS)
    37         throw exception();
    38     for (int i = 0; i < number; i++)
    39     {
    40         cout << "create thread:" << i << endl;
    41         workThread.emplace_back(worker, this);
    42     }
    43 }
    44 template <typename T>
    45 inline threadPool<T>::~threadPool()
    46 {
    47     {
    48         unique_lock<mutex> unique(mt);
    49         stop = true;
    50     }
    51     condition.notify_all();
    52     for (auto &wt : workThread)
    53         wt.join();
    54 }
    55 template <typename T>
    56 bool threadPool<T>::append(T *task)
    57 {
    58     //往任务队列添加任务的时候,要加锁,因为这是线程池,肯定有很多线程
    59     unique_lock<mutex> unique(mt);
    60     taskQueue.push(task);
    61     unique.unlock();
    62     //任务添加完之后,通知阻塞线程过来消费任务,有点像生产消费者模型
    63     condition.notify_one();
    64     return true;
    65 }
    66 template <typename T>
    67 void *threadPool<T>::worker(void *arg)
    68 {
    69     threadPool *pool = (threadPool *)arg;
    70     pool->run();
    71     return pool;
    72 }
    73 template <typename T>
    74 void threadPool<T>::run()
    75 {
    76     while (!stop)
    77     {
    78         unique_lock<mutex> unique(this->mt);
    79         //如果任务队列为空,就停下来等待唤醒,等待另一个线程发来的唤醒请求
    80         while (this->taskQueue.empty())
    81             this->condition.wait(unique);      
    82         T *task = this->taskQueue.front();
    83         this->taskQueue.pop();
    84         if (task)
    85             task->process();
    86     }
    87 }
    88 #endif

    三、线程池代码解析

    对于线程池ThreadPool,必须要有构造和析构函数,构造函数中,创建N个线程(这个自己指定),插入到工作线程当中,工作线程可以是vector结构。工作线程中的线程具体要做什么呢?进入线程的时候必要用unique_lock进程加锁处理,不能让其他线程以及主线程影响到要处理的这个线程。判断任务队列是否为空,如果为空,则利用条件变量中的wait函数来阻塞该线程,等待任务队列不为空之后唤醒它。然后取出任务队列中的任务,执行任务中的具体操作。

    接着将任务放入任务队列taskQueue,这里的任务是外部根据自己的业务自己定义的,可以是对象,可以是函数,结构体等等,而任务队列这里定义为queue结构,一定要记得将任务放入任务队列的时候,要在之前加锁,放入之后在解锁,这里的加锁解锁可以用unique_lock结构,当然也可以用mutex结构,而放入任务队列之后就可以用条件变量的notify_one函数通知阻塞的线程来取任务处理了。

    看过我之前写的《生产消费者模型之条件变量》的朋友对以上代码有点熟悉,没错,线程池的实现就有点像是生产消费者模型,append()就像是生产者,不断的将任务放入队列,run()函数就像消费者,不断的从任务队列中取出任务来处理,生产消费的两头分别用notify_one()和wait()来唤醒和阻塞。更加详细的介绍可以去看我的上一篇文章。

    最后写一个main文件来调用线程池的相关接口,main文件里定义一个任务对象,然后是main函数。

     1 #include "threadPool.h"
     2 #include <string>
     3 using namespace std;
     4 class Task
     5 {
     6 private:
     7     int total = 0;
     8  
     9 public:
    10     void process();
    11 };
    12  
    13 //任务具体实现什么功能,由这个函数实现
    14 void Task::process()
    15 {
    16     //这里就输出一个字符串
    17     cout << "task successful! " << endl;
    18     this_thread::sleep_for(chrono::seconds(1));
    19 }
    20  
    21 template class std::queue<Task>;
    22 int main(void)
    23 {
    24     threadPool<Task> pool(1);
    25     std::string str;
    26     while (1)
    27     {
    28         Task *task = new Task();
    29         pool.append(task);
    30         delete task;
    31     }
    32 }

    以上就是线程池的实现部分,充分利用条件变量和互斥锁来实现,模型可以参考生产消费者模型。以上代码部分来自网络,根据自己的需求更改一些。

    更多精彩内容,请关注同名公众:一点月光(alittle-moon)

  • 相关阅读:
    第3章 Spring AOP
    第2章 Spring中的Bean
    第1章 Spring的应用
    Codeforces Round #558 (Div. 2)-Cat Party (Hard Edition)-(前缀和 + 模拟)
    Codeforces Round #552 (Div. 3)-1154E-Two Teams-(模拟+双指针)
    Codeforces Round #552 (Div. 3)-D-Walking Robot-(贪心)
    Codeforces Round #552 (Div. 3)-C-Gourmet Cat
    Codeforces Round #555 (Div. 3)
    2019年湘潭大学程序设计竞赛(重现赛)
    Buy Fruits-(构造)
  • 原文地址:https://www.cnblogs.com/kiwiblog/p/14187775.html
Copyright © 2011-2022 走看看