C++11线程池的简单实现

编译环境

  1. clion + cygwin 以及 ubuntu 20.04 + g++ 9.3.0 都fine
  2. 线程的创建直接使用了c++11 std::thread库
  3. 互斥量和条件变量亦是基于c++11的各种库。
    1. std::mutex;
    2. std::unique_lock;
    3. std::lock_guard;
    4. std::condition_variable;

整体设计

1、目标任务函数

一个简单的函数,用来测试,这个函数即为每个线程不断轮循执行的函数

void *fooWorkFunction(void *args) {
    int *p = (int *) args;
    //输出线程ID
    std::cout << "Thread Number : " << std::this_thread::get_id() << endl;
    //将传入的参数输出
    std::cout << "Thread Work : " << *p << endl;
    //sleep 1秒,用来观察
    sleep(1);
    return NULL;
}

2、单个任务的实现

  • 线程池在取出任务后的实际操作就是执行任务结构中定义的Job.function函数:funcOfaJob(arg)
  • function的具体实现则自定义,在funcOfaJob{}的函数定义内自行处理,将arg转为自己需要的类型
struct Job {
    std::function<void*(void *)> funcOfaJob;
    void *args;

    Job(std::function<void *(void *)> inputFun, void *arg) {
        funcOfaJob = inputFun;
        args = arg;
    }

    Job() {
        funcOfaJob = nullptr;
        args = nullptr;
    }
};

3、任务队列类的实现

  • 这一步我们要实现的是,维护一个队列,在线程池的角度来看,该队列是线程池对象的一个成员变量;而在我们实际实现时,会先将其封装成一个类,并添加如下几个函数:
    1. void addTask(Job) {}函数:用于往队列中添加函数
    2. Job takeTask() {}函数:用于取出队列
    3. 额外的一些非必须函数,比如获取队列中任务的数量之类的,但不是必须的
  • 工作时持续将任务添加到队列中,同时线程池中的线程去取任务来执行(实际情况是线程的入口函数是一个死循环,会不停判断队列中是否 为空/有任务 Job,如果有的话就将其取出,并执行Job中的函数Job.funcOfaJob
  • 对于这个队列,本文用的是std::list<Job>,当然也可以换成std::queue<Job>或者std::deque<Job>甚至std::vector<Job>,不过考虑到时间复杂度,list是更好的选择
  • 在多线程时必然会存在资源竞争的情况,因此对于这个队列中任务的添加和取出,需要加锁;不过我们不在队列类中直接加锁,而是在ThreadPool类中加锁,当然原理都是一样的
  • 队列类的实现如下:
class TaskQueue {
public:
    TaskQueue() {};
    ~TaskQueue() {};
    
    //第一种addTask()
    void addTask(Job &job) {
        _taskQueue.push_back(job);
    }
    
    //第二种addTask()
    void addTask(std::function<void*(void *)> func, void *arg) {
        Job job = Job(func, arg);
        _taskQueue.push_back(job);
    }

    Job takeTask() {
        auto jobToReturn = _taskQueue.front();
        _taskQueue.pop_front();
        return jobToReturn;
    }

    int getTaskNumber() {
        return _taskQueue.size();
    }

private:
    std::list<Job> _taskQueue;
};

线程池类的实现

  • 对于一个线程池类的实现,我们需要的函数有:
  1. 构造函数 : 初始化时我们要考虑 1)线程的数量,我们要在构造函数内完成线程的创建
  2. 析构函数 : 析构时我们要考虑线程的回收
  3. 线程的入口函数 : 线程的入口函数会是全局函数或者类的静态成员函数,具体为什么要是静态函数或者全局函数,是因为线程具有独立的栈空间,如果在主线程中创建新的线程并传入一个主线程核中类对象的成员函数,由于在新的线程中该对象不存在,也无法找到该对象的成员函数地址;(此处仅是个人的理解,诸位可以自行查阅资料
  4. 一个添加任务的函数 : 因为在TaskQueue类中我们没有加锁,因此这一步要加锁
  • 同样,我们需要的成员变量有:
  1. 一个上文中TaskQueue类的对象实体
  2. 一个保存所有线程对象的数组,这里用的是std::vector<std::thread>
  3. 一个std::mutex
  4. 一个条件变量std::condition_variable

整体的线程池 ThreadPool类的代码如下:

class ThreadPool {
public:
    ThreadPool(int threadSize) {
        // std::thread th(ThreadPool::worker, this);
        for (int i = 0; i < 2; i++) {
            std::lock_guard<std::mutex> lg(this->_mutexInPool);
            this->_threads.emplace_back(std::thread(ThreadPool::worker, this));
        }
    }

    ~ThreadPool() {
        for (auto &t : this->_threads) {
            t.join();
        }
    }

    static void *worker(void *args);

    void appendTask(std::function<void *(void *)> func, void *args) {
        std::lock_guard<std::mutex> lg(this->_mutexInPool);
        Job j(func, args);
        this->_taskQueueInPool.addTask(j);
        this->_cvInPool.notify_all();
    }

    void run() {
        while (1) {
            Job j;
            {
                std::unique_lock<std::mutex> lg(this->_mutexInPool);
                while (this->_taskQueueInPool.getTaskNumber() == 0) {
                    this->_cvInPool.wait(lg);
                }
                j = this->_taskQueueInPool.takeTask();
                j.funcOfaJob(j.args);
            }

        }
    }

public:
    TaskQueue _taskQueueInPool;
    vector<std::thread> _threads;
    std::mutex _mutexInPool;
    std::condition_variable _cvInPool;
};

void *ThreadPool::worker(void *args) {
    ThreadPool *pool = static_cast<ThreadPool *>(args);
//    cout << pool->_tashQueueInPool.getTaskNumber() << endl;
    pool->run();
    return nullptr;
}

跑起来

main()函数中,我们来一个简单的循环用来测试

int main() {
    ThreadPool *tp = new ThreadPool(1);
    for (int i = 0; i < 10; i++) {
        int* tmp = new int(i);
        tp->appendTask(fooWorkFunction, tmp);
    }

    delete tp;
    return 0;
}

注意事项 & 全部代码 attention please

  • 注意事项:
  1. 为了安全,线程池应该是单例的,但是这里我就没有写成单例模式了
  2. 整体代码跑起来是会持续运行的,我假设任务会一直不断添加,可以在run()函数中设置合适的时间break;
  3. 整体代码如下
#include <vector>
#include <iostream>
#include <functional>
#include <mutex>
#include <list>
#include <thread>
#include <unistd.h>
#include <atomic>
#include <condition_variable>

using namespace std;

void *fooWorkFunction(void *args) {
    int *p = (int *) args;
    std::cout << "Thread Number : " << std::this_thread::get_id() << endl;
    std::cout << "Thread Work : " << *p << endl;
    sleep(1);
    return NULL;
}

struct Job {
    std::function<void*(void *)> funcOfaJob;
    void *args;

    Job(std::function<void *(void *)> inputFun, void *arg) {
        funcOfaJob = inputFun;
        args = arg;
    }

    Job() {
        funcOfaJob = nullptr;
        args = nullptr;
    }
};

class TaskQueue {
public:
    TaskQueue() {};

    ~TaskQueue() {};

    void addTask(Job &job) {
        _taskQueue.push_back(job);
    }

    void addTask(std::function<void*(void *)> func, void *arg) {
        Job job = Job(func, arg);
        _taskQueue.push_back(job);
    }

    Job takeTask() {
        auto jobToReturn = _taskQueue.front();
        _taskQueue.pop_front();
        return jobToReturn;
    }

    int getTaskNumber() {
        return _taskQueue.size();
    }

private:
    std::list<Job> _taskQueue;
};

class ThreadPool {
public:
    ThreadPool(int threadSize) {
        // std::thread th(ThreadPool::worker, this);
        for (int i = 0; i < 2; i++) {
            std::lock_guard<std::mutex> lg(this->_mutexInPool);
            this->_threads.emplace_back(std::thread(ThreadPool::worker, this));
        }
    }

    ~ThreadPool() {
        for (auto &t : this->_threads) {
            t.join();
        }
    }

    static void *worker(void *args);

    void appendTask(std::function<void *(void *)> func, void *args) {
        std::lock_guard<std::mutex> lg(this->_mutexInPool);
        Job j(func, args);
        this->_taskQueueInPool.addTask(j);
        this->_cvInPool.notify_all();
    }

    void run() {
        while (1) {
            Job j;
            {
                std::unique_lock<std::mutex> lg(this->_mutexInPool);
                while (this->_taskQueueInPool.getTaskNumber() == 0) {
                    this->_cvInPool.wait(lg);
                }
                j = this->_taskQueueInPool.takeTask();
                j.funcOfaJob(j.args);
            }

        }
    }

public:
    TaskQueue _taskQueueInPool;
    vector<std::thread> _threads;
    std::mutex _mutexInPool;
    std::condition_variable _cvInPool;
};

void *ThreadPool::worker(void *args) {
    ThreadPool *pool = static_cast<ThreadPool *>(args);
//    cout << pool->_tashQueueInPool.getTaskNumber() << endl;
    pool->run();
    return nullptr;
}

int main() {
    ThreadPool *tp = new ThreadPool(1);
    for (int i = 0; i < 10; i++) {
        int* tmp = new int(i);
        tp->appendTask(fooWorkFunction, tmp);
    }

    cout << "xxxxxx" << endl;

    delete tp;
    return 0;
}
posted @ 2021-09-08 22:03  wztuuu  阅读(311)  评论(0)    收藏  举报