C++线程池介绍和C++代码实现

1、介绍

1.1 线程池应用场景

  在进行创建线程任务时,如果需要频繁的创建线程、销毁线程,这样会极大地降低效率,因为创建线程也是需要时间的,一个完整的线程处理运行时间包括:线程的创建时间、线程运作时间、线程的销毁时间。

      对于频繁创建线程的业务场景,我们可以预先创建多个线程,在创建多线程任务时,我们可以直接将线程函数添加到预先创建的线程中,这样就可以避免多次创建线程的时间,提高代码运行效率。

1.2 线程池设计的思路

        设计多线程主要实现的功能有:(1)预设创建线程的数量。(2)存储运行一定数量线程任务容器workerThread变量(可以是vector或其他容器,元素类型是std::thread),该容器一直检测任务队列变量中是否有待执行的任务,有:则取出执行,没有:则等待。(3)用于存储运行任务的变量Task(数据类型是queue,数据类型是std::function<T>),该变量主要用于存储待运行线程函数func。

线程池理论可以参考链接如下:

深入解析C++编程中线程池的使用_c++线程池用法_歌行梅村的博客-CSDN博客

1.3 线程池设计注意事项

所需理论知识

创建具有适配所有线程函数的任务队列Task,创建这样的任务队列需要有一定的C++基础,可能需要如下知识点:

可变参数模板:template <class... Args>_我在这里啊@的博客-CSDN博客

 C++多线程之旅-future等待事件_或许 没有的博客-CSDN博客

C++中函数对象模板function<T>、通用函数适配器std::bind和lambda_c++function头文件_夜雨听萧瑟的博客-CSDN博客

 C++多线程中共享变量同步问题_夜雨听萧瑟的博客-CSDN博客

1.4 预设的线程数量是多少?

        预设的线程数量不是说越多越好,而是创建适当数量的线程数,让CPU的利用率达到最大。如果预设的线程数量过大,PC的核数有限,这样同时只会有一小部分任务在同步运行,这样操作系统就需要不断的切换上下文,频繁的切换上下文也需要时间,这样反而会降低运行效率。

 经验值

主要有下面几种:

(1) 设置线程数量的一般经验值为:2N(N是CPU核数)

(2)  2N+1(N是CPU核数)

(3)  N+1(N是CPU核数)

 具体设置数量可以在设置后,对其不同数量线程数运行效率进行简单的测试。

 具体分析

可参考链接:

线程池创建线程数量讨论_夜雨听萧瑟的博客-CSDN博客

2、简单demo

(1)该demo的中心思想是创建10个运行的线程函数的容器workerThread,该线程不断检测任务队列中是否有待处理任务,有待处理任务则取出,执行任务。(2)创建一个管理任务队列数量容器Task,用户可以向其添加任务。

threadpoolm.h文件如下:

#pragma once
#include <mutex>
#include <condition_variable>
#include <thread>
#include <vector>
#include <queue>
#include <functional>


class threadPoolM
{

public:
    using funcType = std::function<void()>;
    threadPoolM();
    ~threadPoolM();
    void setThreadNum(int num);
    void AddTask(const funcType& pf);
private:
    void StartWork();
    void RunTask();

    int m_num;
    std::vector<std::thread>workerThread;
    std::mutex mut;
    std::condition_variable cond;
    std::queue<funcType> Task;
};

threadpoolm.cpp文件如下:

#include "threadpoolm.h"
#include <iostream>
threadPoolM::threadPoolM()
{

}

threadPoolM::~threadPoolM()
{
    for(int i = 0; i < workerThread.size(); i++)
    {
        workerThread.at(i).join();
    }
}

void threadPoolM::setThreadNum(int num)
{
    m_num = num;
    StartWork();
}

void threadPoolM::AddTask(const threadPoolM::funcType& pf)
{

    std::unique_lock<std::mutex>lk(mut);
    cond.wait(lk,[this]{return Task.size() < 10;});

    Task.push(pf);
    std::cout <<"id " << std::this_thread::get_id() << ", add a task, size is  " << Task.size() << std::endl;
    cond.notify_one();

}

void threadPoolM::StartWork()
{
    for(int i = 0; i < m_num; i++)
    {
        workerThread.push_back(std::thread(&threadPoolM::RunTask,this));
    }
}

void threadPoolM::RunTask()
{
    while (true) {
        std::unique_lock<std::mutex>lk(mut);
        cond.wait(lk,[this]{return Task.size()>0;});
        auto Ta = std::move(Task.front());
        Task.pop();
        Ta();
        std::cout <<"id " << std::this_thread::get_id() << ", run a task,size" << Task.size() << std::endl;
        cond.notify_one();
    }
}

上面线程池类的使用main.cpp如下:

#include <iostream>
#include "threadpoolm.h"
int cnt = 0;

void printId(int id)
{
    std::cout << "id " << std::this_thread::get_id() << ", id" << id << std::endl;
}
int main()
{
    std::cout << "Hello World!" << std::endl;

    threadPoolM pool;
    pool.setThreadNum(10);

    while(true)
    {
        if(cnt++ > 2000)
        {
            cnt = 0;
        }
        std::cout << "cnt " << cnt << std::endl;
        auto f1 = std::bind(printId,cnt);
        pool.AddTask(f1); 
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        //主线程中的while循环,只是对实际添加任务消息进行模拟,故没有退出while条件。
    }

    return 0;  //由于线程池中的RunTask函数存在while循环,没有退出条件,所以该行代码不会执行。可以按照实际条件对其while条件修改。
}

 运行结果如下:

 上面的代码参考于

c++11最简单的线程池实现_c++实现线程池_osDetach的博客-CSDN博客

 线程池的实现代码也可参考下面链接:

基于c++11的100行实现简单线程池_c++11 100行实现线程池 csdn_6plus的博客-CSDN博客

附加知识 

怎样理解线程的睡眠,挂起,和阻塞? - 知乎 (zhihu.com)

“阻塞(pend)”与“挂起(suspend)”的区别?_pend group run cpu调度_zhch152的博客-CSDN博客

猜你喜欢

转载自blog.csdn.net/hanxiaoyong_/article/details/130846589