C++新特性实现高效线程池,程序性能瞬间提升!

一、引言

C++实现的线程池,可能涉及以下知识点:

  • decltype。
  • packaged_task。
  • make_shared。
  • mutex。
  • unique_lock。
  • notify_one。
  • future。
  • queue。
  • bind。
  • thread。
    等等。

线程池逻辑

二、线程池的接口设计

(1)封装一个线程池的类。
(2)线程池的初始化:设置线程的数量。
(3)启动线程池:创建线程等工作。
(4)执行任务的函数。
(5)停止线程池。
(6)等所有任务执行完成,退出执行函数。

2.1、类封装

线程池类,采用c++11来实现。

#ifndef _CPP_THREAD_POOL_H_
#define _CPP_THREAD_POOL_H_

#include <iostream>
#include <functional>
#include <memory>
#include <queue>
#include <mutex>
#include <vector>
#include <thread>
#include <future>

#ifdef WIN32
#include <windows.h>
#else
#include <sys/time.h>
#endif

using namespace std;

void getNow(timeval *tv);
int64_t getNowMs();

#define TNOW    getNow()
#define TNOWMS  getNowMs()

class CPP_ThreadPool{
protected:
    struct TaskFunc{
        TaskFunc(uint64_t expireTime):_expireTime(expireTime){}
        int64_t _expireTime=0;//超时的绝对时间
        function<void()> _func;
    };
    typedef shared_ptr<TaskFunc> TaskFuncPtr;

    /* 
     * @brief 获取任务 ** 
     *@return TaskFuncPtr 
     */
    bool get(TaskFuncPtr& task);

    /*
    * @brief 线程池是否退出
    */
    bool isTerminate()
    {
        return _bTerminate;
    }

    /*
    * @brief 线程运行态
    */
   void run();

public: 
    /*
     * @brief 构造函数 
     */
    CPP_ThreadPool(); 

    /* 
    * @brief 析构, 会停止所有线程 
    */
    virtual ~CPP_ThreadPool();

    /* 
     * * @brief 初始化. 
     * * @param num 工作线程个数 
     */
    bool init(size_t num);

    /*
    * @brief 停止所有线程, 会等待所有线程结束 
    */
   void stop();

   /*
   * @brief 启动所有线程 
   */
    bool start();

    /* 
     * @brief 等待当前任务队列中, 所有工作全部结束(队列无任务). 
     * @param millsecond 等待的时间(ms), -1:永远等待 
     * @return true, 所有工作都处理完毕 
     * false,超时退出 
     */
    bool waitForAllDone(int millsecond=-1);

   /*
    * @brief 获取线程个数.
    * @return size_t 线程个数 
    */
   size_t getThreadNum()
   {
        unique_lock<mutex> lock(_mutex);
        return _threads.size();
   }

   /*
    *  @brief 获取当前线程池的任务数
    * @return size_t 线程池的任务数 
    */
   size_t getJobNum()
   {
        unique_lock<mutex> lock(_mutex);
        return _tasks.size();
   }

   /*
   * @brief 用线程池启用任务(F是function, Args是参数) ** 
   * @param ParentFunctor 
   * @param tf 
   * @return 返回任务的future对象, 可以通过这个对象来获取返回值 
   */
    template <class F,class... Args>
    auto exec(F&& f, Args&&... args)->future<decltype(f(args...))>
    {
        return exec(0,f,args...);
    }

    /* 
    * unused.
    *
    * @brief 用线程池启用任务(F是function, Args是参数) 
    * @param 超时时间 ,单位ms (为0时不做超时控制) ;若任务超时,此任务将被丢弃 
    * @param bind function 
    * @return 返回任务的future对象, 可以通过这个对象来获取返回值 
    *
    * template <class F, class... Args> 
    * 它是c++里新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数 
    * auto exec(F &&f, Args &&... args) -> std::future<decltype(f(args...))> 
    * std::future<decltype(f(args...))>:返回future,调用者可以通过future获取返回值 
    * 返回值后置
    */
    template<class F,class... Args>
    auto exec(int64_t timeoutMs,F&& f,Args&&... args) -> future<decltype(f(args...))>
    {
        //获取现在时间
        int64_t expireTime=(timeoutMs==0)?0:TNOWMS+timeoutMs;
        // 定义返回值类型
        using retType=decltype(f(args...));
        // 封装任务
        auto task=make_shared<packaged_task<retType()>>(bind(forward<F>(f),forward<Args>(args)...));
        // 封装任务指针,设置过期时间
        TaskFuncPtr fPtr=make_shared<TaskFunc>(expireTime);
        fPtr->_func=[task](){
            (*task)();
        };

        unique_lock<mutex> lock(_mutex);
        // 插入任务
        _tasks.push(fPtr);
        // 唤醒阻塞的线程,可以考虑只有任务队列为空的情 况再去notify
        _condition.notify_one();

        return task->get_future();
    }

protected:
    size_t  _threadNum;//线程数量
    bool    _bTerminate;//判定是否终止线程池
    mutex   _mutex;     //唯一锁
    vector<thread*> _threads;   //工作线程数组
    queue<TaskFuncPtr> _tasks;  //任务队列
    condition_variable _condition;//条件变量
    atomic<int>         _atomic{0};//原子变量
};


#endif

使用示例:

CPP_ThreadPool tpool; 
tpool.init(5); //初始化线程池线程数 
//启动线程方式 
tpool.start(); 
//将任务丢到线程池中* 
tpool.exec(testFunction, 10); //参数和start相同 
//等待线程池结束 
tpool.waitForAllDone(1000); //参数<0时, 表示无限等待(注意有人调用stop也会推出) 
//此时: 外部需要结束线程池是调用 
tpool.stop(); 

注意:ZERO_ThreadPool::exec执行任务返回的是个future, 因此可以通过future异步获取结果, 比如:

int testInt(int i) 
{ 
    return i; 
} 
auto f = tpool.exec(testInt, 5); 
cout << f.get() << endl; //当testInt在线程池中执行后, f.get()会返回数值5 

class Test 
{ 
public: 
    int test(int i); 
}; 
Test t; 
auto f = tpool.exec(std::bind(&Test::test, &t, std::placeholders::_1), 10); 
//返回的future对象, 可以检查是否执行 
cout << f.get() << endl; 

2.2、线程池的初始化

主要是设置线程池中线程的数量,如果线程池已经存在则直接返回,防止重复初始化。

bool CPP_ThreadPool::init(size_t num)
{
    unique_lock<mutex> lock(_mutex);
    if(!_threads.empty())
        return false;
    _threadNum=num;
    return true;
}

2.3、线程池的启动

根据设置的线程数量,创建线程并保存在一个数组中。如果线程池已经存在则直接返回,防止重复启动。

bool CPP_ThreadPool::start()
{
    unique_lock<mutex> lock(_mutex);
    if(!_threads.empty())
        return false;
    
    for(size_t i=0;i<_threadNum;i++)
    {
        _threads.push_back(new thread(&CPP_ThreadPool::run,this));
    }
    return true;
}

2.4、线程池的停止

设置线程退出条件,并通知所有线程。停止时,要等待所有线程都执行完任务,再销毁线程。
需要注意锁的粒度。

void CPP_ThreadPool::stop()
{
    // 注意要有这个{},不然会死锁。
    {
        unique_lock<mutex> lock(_mutex);
        _bTerminate=true;
        _condition.notify_all();
    }
    size_t thdCount=_threads.size();
    for(size_t i=0;i<thdCount;i++)
    {
        if(_threads[i]->joinable())
        {
            _threads[i]->join();
        }
        delete _threads[i];
        _threads[i]=NULL;
    }
    unique_lock<mutex> lock(_mutex);
    _threads.clear();
}

2.5、线程的执行函数run()

读取任务:判断任务是否存在,如果任务队列为空,则进入等待状态直到任务队列不为空或退出线程池(这里需要两次判断,因为可能存在虚假唤醒)。
执行任务:调用匿名函数。
检测所有任务都是否执行完毕:这里使用了原子变量来检测任务是否都执行完,原因在于任务队列为空不代表任务已经执行完(任务可能还在运行中、也可能是任务刚弹出还没运行),使用原子变量来计数就更严谨。

bool CPP_ThreadPool::get(TaskFuncPtr& task)
{
    unique_lock<mutex> lock(_mutex);
    if(_tasks.empty())//判断任务是否存在
    {
        _condition.wait(lock,[this]{
            return _bTerminate || !_tasks.empty();//唤醒条件
        });
    }

    if(_bTerminate)
        return false;

    if(!_tasks.empty())//判断任务是否存在
    {
        task=move(_tasks.front());// 使用移动语义
        _tasks.pop();//弹出一个任务
        return true;
    }
    return false;
}

// 执行任务的线程
void CPP_ThreadPool::run()
{
    while(!isTerminate())
    {
        TaskFuncPtr task;
        // 读取任务
        bool ok=get(task);
        if(ok)
        {
            ++_atomic;
            try
            {
                if(task->_expireTime!=0 && task->_expireTime < TNOWMS)
                {
                    // 处理超时任务
                }
                else
                    task->_func();//执行任务
            }
            catch(...)
            {}
            --_atomic;
            // 任务执行完毕,这里只是为了通知waitForAllDone
            unique_lock<mutex> lock(_mutex);
            if(_atomic==0 && _tasks.empty())
                _condition.notify_all();
        }
    }
}

2.6、任务的运行函数

这里使用了可变模块参数、智能指针、bind、function、捕获列表的相关技术知识。
返回任务的future对象, 可以通过这个对象来获取返回值。
超时时间 ,单位ms (为0时不做超时控制) ;若任务超时,此任务将被丢弃。
可变模块参数对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数。

  /*
   * @brief 用线程池启用任务(F是function, Args是参数) ** 
   * @param ParentFunctor 
   * @param tf 
   * @return 返回任务的future对象, 可以通过这个对象来获取返回值 
   */
    template <class F,class... Args>
    auto exec(F&& f, Args&&... args)->future<decltype(f(args...))>
    {
        return exec(0,f,args...);
    }

    /* 
    * unused.
    *
    * @brief 用线程池启用任务(F是function, Args是参数) 
    * @param 超时时间 ,单位ms (为0时不做超时控制) ;若任务超时,此任务将被丢弃 
    * @param bind function 
    * @return 返回任务的future对象, 可以通过这个对象来获取返回值 
    *
    * template <class F, class... Args> 
    * 它是c++里新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数 
    * auto exec(F &&f, Args &&... args) -> std::future<decltype(f(args...))> 
    * std::future<decltype(f(args...))>:返回future,调用者可以通过future获取返回值 
    * 返回值后置
    */
    template<class F,class... Args>
    auto exec(int64_t timeoutMs,F&& f,Args&&... args) -> future<decltype(f(args...))>
    {
        //获取现在时间
        int64_t expireTime=(timeoutMs==0)?0:TNOWMS+timeoutMs;
        // 定义返回值类型
        using retType=decltype(f(args...));
        // 封装任务
        auto task=make_shared<packaged_task<retType()>>(bind(forward<F>(f),forward<Args>(args)...));
        // 封装任务指针,设置过期时间
        TaskFuncPtr fPtr=make_shared<TaskFunc>(expireTime);
        fPtr->_func=[task](){
            (*task)();
        };

        unique_lock<mutex> lock(_mutex);
        // 插入任务
        _tasks.push(fPtr);
        // 唤醒阻塞的线程,可以考虑只有任务队列为空的情 况再去notify
        _condition.notify_one();

        return task->get_future();
    }

2.7、等待所有线程结束

bool CPP_ThreadPool::waitForAllDone(int millsecond)
{
    unique_lock<mutex> lock(_mutex);
    if(_tasks.empty())
        return true;
    if(millsecond<0)
    {
        _condition.wait(lock,[this]{ return _tasks.empty();});
        return true;
    }
    else
    {
        return _condition.wait_for(lock,chrono::milliseconds(millsecond),[this]{ return _tasks.empty();});
    }
}

三、测试线程池

#include <iostream>
#include "cppThreadPool.h"

using namespace std;

void func1(int a) 
{ 
    cout << "func1() a=" << a << endl; 
}
void func2(int a, string b) 
{ 
    cout << "func2() a=" << a << ", b=" << b<< endl; 
}

void func3()
{
    cout<<"func3"<<endl;
}

void test01()
{
    cout<<"test 01"<<endl;
    CPP_ThreadPool threadpool;
    threadpool.init(2);
    threadpool.start();//启动线程池
    // 执行任务
    threadpool.exec(func1,10);
    threadpool.exec(func2,20,"FLY.");
    threadpool.exec(1000,func3);


    threadpool.waitForAllDone();
    threadpool.stop();
}

int func1_future(int a) 
{ 
    cout << "func1() a=" << a << endl; 
    return a; 
}

string func2_future(int a, string b) 
{ 
    cout << "func2() a=" << a << ", b=" << b<< endl; 
    return b; 
}

void test02()
{
    cout<<"test 02"<<endl;
    CPP_ThreadPool threadpool;
    threadpool.init(2);
    threadpool.start();//启动线程池

    future<decltype(func1_future(0))> ret01=threadpool.exec(func1_future,10);
    future<string> ret02=threadpool.exec(func2_future,20,"FLY.");

    threadpool.waitForAllDone();

    cout<<"ret01 = "<<ret01.get()<<endl;
    cout<<"ret02 = "<<ret02.get()<<endl;

    
    threadpool.stop();

}

class Test{
public:
    int test(int a)
    {
        cout<<_name<<": a = "<<a<<endl;
        return a+1;
    }
    void setname(string name)
    {
        _name=name;
    }
    string _name;

};

void test03()
{
    cout<<"test 03"<<endl;
    CPP_ThreadPool threadpool;
    threadpool.init(2);
    threadpool.start();//启动线程池
    Test t1;
    Test t2;
    t1.setname("Test 1");
    t2.setname("Test 2");
    auto f1=threadpool.exec(bind(&Test::test,&t1,placeholders::_1),10);
    auto f2=threadpool.exec(bind(&Test::test,&t2,placeholders::_1),20);

    threadpool.waitForAllDone();
    cout<<"f1 = "<<f1.get()<<endl;
    cout<<"f2 = "<<f2.get()<<endl;
    threadpool.stop();

}

int main(int argc,char **argv)
{
    // 简单测试线程池
    test01();
    // 测试任务函数返回值
    test02();
    // 测试类对象函数的绑定
    test03();

    return 0;
}

执行结果:

test 01
func1() a=10
func2() a=20, b=FLY.
func3
test 02
func1() a=10
func2() a=20, b=FLY.
ret01 = 10
ret02 = FLY.
test 03
Test 1: a = 10
Test 2: a = 20
f1 = 11
f2 = 21

四、源码地址

源码已经上传github。可以点击超链接或者关注博主《Lion 莱恩呀》获取。

总结

线程池的核心:初始化、线程启动、执行函数、线程停止。
通过本文的学习,能够深入了解C++提供的新特性,并在实际项目中熟练运用这些特性来构建高效的线程池。对于需要应对高并发场景的程序,合理使用线程池可以有效地提升程序性能,减少资源的浪费,并且使程序具备更好的扩展性和稳定性。C++新特性为线程池的实现提供了强大的支持,使得程序性能可以瞬间提升,为开发工作带来极大的便利和有效性。

在这里插入图片描述

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 实现线程池的方法有很多,但是最常见的方法是使用队列来维护任务。每个线程都在队列中等待任务,当有新任务到达时,就从队列中取出一个任务并执行。这样,每个线程都可以在并行执行任务,而不需要创建新的线程。 在C语言实现线程池的代码如下: ``` #include <pthread.h> #include <stdio.h> #include <stdlib.h> #define NUM_THREADS 5 void *print_hello(void *threadid) { long tid; tid = (long)threadid; printf("Hello World! It's me, thread #%ld!\n", tid); pthread_exit(NULL); } int main(int argc, char *argv[]) { pthread_t threads[NUM_THREADS]; int rc; long t; for (t = 0; t < NUM_THREADS; t++) { printf("In main: creating thread %ld\n", t); rc = pthread_create(&threads[t], NULL, print_hello, (void *)t); if (rc) { printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); } } pthread_exit(NULL); } ``` ### 回答2: 要实现线程池,首先需要先了解线程池的基本概念和原理。 线程池是一种用来管理和复用线程的技术,它能够维护一个线程队列,按需创建和销毁线程,并将任务分配给这些线程来执行。使用线程池可以提高程序的性能,减少线程创建和销毁的开销。 在C语言中,可以使用多线程的库来实现线程池,比如pthread库。下面是一个简单的用C语言实现线程池的步骤: 1. 定义线程池结构体:创建一个结构体来保存线程池的相关信息,如线程池的大小、任务队列、互斥锁、条件变量等。 2. 初始化线程池:在初始化函数中,需要对线程池中的各个成员进行初始化,如创建指定数量的线程、初始化互斥锁和条件变量等。 3. 定义任务函数:线程池的任务函数用于处理任务队列中的任务,根据具体需求来定义任务的执行逻辑。 4. 添加任务到线程池:当有新的任务时,将任务添加到任务队列中,并通过条件变量来通知线程池中的线程有新任务可执行。 5. 线程池中的线程获取任务并执行:在线程中循环检查任务队列,当有任务时,线程从任务队列中获取任务并执行。 6. 销毁线程池:在停止使用线程池时,要销毁线程池中的资源,包括线程的回收、互斥锁和条件变量的销毁等。 通过以上步骤,就可以在C语言实现一个简单的线程池。具体实现中还需要考虑线程同步、任务队列的管理等问题,以确保线程池的稳定性和性能。 ### 回答3: 线程池是用来管理和复用线程的一种机制,可以更有效地使用系统资源和提高应用程序的性能。下面是使用C语言实现线程池的一般步骤: 1. 定义一个线程池的结构体,包含线程池的状态、大小、最大线程数、工作任务队列等信息。 ```c typedef struct { pthread_t *threads; // 线程数组 int thread_count; // 线程数 int max_threads; // 最大线程数 int pool_size; // 线程池大小 int shutdown; // 关闭标志 pthread_mutex_t mutex; // 互斥锁 pthread_cond_t notify; // 条件变量 Task *task_queue; // 任务队列 } ThreadPool; ``` 2. 初始化线程池,创建指定数量的线程。 ```c ThreadPool* thread_pool_init(int pool_size) { ThreadPool *pool = malloc(sizeof(ThreadPool)); pool->threads = malloc(pool_size * sizeof(pthread_t)); pool->thread_count = pool_size; pool->max_threads = pool_size; pool->pool_size = 0; pool->shutdown = 0; // 初始化互斥锁和条件变量 pthread_mutex_init(&(pool->mutex), NULL); pthread_cond_init(&(pool->notify), NULL); // 创建线程 for (int i = 0; i < pool_size; i++) { pthread_create(&(pool->threads[i]), NULL, thread_worker, (void*)pool); } return pool; } ``` 3. 定义线程工作函数,不断从任务队列中取出任务执行。 ```c void* thread_worker(void *arg) { ThreadPool *pool = (ThreadPool*)arg; while (1) { pthread_mutex_lock(&(pool->mutex)); // 线程池关闭,退出线程 while (pool->pool_size == 0 && !pool->shutdown) { pthread_cond_wait(&(pool->notify), &(pool->mutex)); } if (pool->shutdown) { pthread_mutex_unlock(&(pool->mutex)); pthread_exit(NULL); } // 从任务队列中取出任务执行 Task *task = pool->task_queue; pool->task_queue = pool->task_queue->next; pool->pool_size--; pthread_mutex_unlock(&(pool->mutex)); task->func(task->arg); free(task); } pthread_exit(NULL); } ``` 4. 定义任务结构体,包含任务函数指针和参数。 ```c typedef struct Task { void (*func)(void*); void *arg; struct Task *next; } Task; ``` 5. 向线程池中添加任务。 ```c void thread_pool_add_task(ThreadPool *pool, void (*func)(void*), void *arg) { Task *task = malloc(sizeof(Task)); task->func = func; task->arg = arg; task->next = NULL; pthread_mutex_lock(&(pool->mutex)); if (pool->task_queue == NULL) { pool->task_queue = task; } else { Task *cur = pool->task_queue; while (cur->next != NULL) { cur = cur->next; } cur->next = task; } pool->pool_size++; pthread_mutex_unlock(&(pool->mutex)); pthread_cond_signal(&(pool->notify)); } ``` 6. 关闭线程池。 ```c void thread_pool_shutdown(ThreadPool *pool) { if (pool == NULL) { return; } pthread_mutex_lock(&(pool->mutex)); pool->shutdown = 1; pthread_mutex_unlock(&(pool->mutex)); pthread_cond_broadcast(&(pool->notify)); // 等待线程退出 for (int i = 0; i < pool->thread_count; i++) { pthread_join(pool->threads[i], NULL); } // 释放资源 free(pool->threads); while (pool->task_queue != NULL) { Task *next = pool->task_queue->next; free(pool->task_queue); pool->task_queue = next; } pthread_mutex_destroy(&(pool->mutex)); pthread_cond_destroy(&(pool->notify)); free(pool); } ``` 以上是一个简单的线程池实现,通过初始化线程池、添加任务、关闭线程池等操作,可以有效地管理和复用线程,提高应用程序的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion Long

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值