c++实现简单线程池

 

 主要结合操作系统的基本原理和c++11特性来写

首先了解一下lambda表达式,利用Lambda表达式,可以方便的定义和创建匿名函数

捕获外部变量有3种方法

1.值捕获

int main()
{
    int a = 123;
    auto f = [a] { cout << a << endl; }; 
    a = 321;
    f(); // 输出:123
}

 

2.引用捕获

int main()
{
    int a = 123;
    auto f = [&a] { cout << a << endl; }; 
    a = 321;
    f(); // 输出:321
}

 3.隐式捕获

int main()
{
    int a = 123;
    auto f = [=] { cout << a << endl; };    // 值捕获
    f(); // 输出:123
}

int main()
{
    int a = 123;
    auto f = [&] { cout << a << endl; };    // 引用捕获
    a = 321;
    f(); // 输出:321
}

 类型尾置

让编译器在函数定义的时候知道返回类型

template <typename T>
auto &getItem(T begin, T end) -> decltype(*begin) {
    return *begin; // 返回序列中一个元素的引用
}

下面就介绍一下c++11的特性

#include <iostream>
#include <thread>
void foo() {
    std::cout << "hello world" << std::endl;
}
int main() {
    std::thread t(foo);//创建一个线程实例
    t.join();//加入一个线程
    return 0;
}

/*
std::mutex mutex 创建一个互斥量
std::lock_guard<std::mutex> lock(mutex); 对互斥量上锁
std::unique_lock 也是上锁,但更灵活
std::packaged_task<int()> task([](){return 7;}); 用来封装任何可以调用的目标,从而用于实现异步的调用,异步即主线程A想获取某个计算结果而调用线程B
std::future<int> result = task.get_future();   用来获取异步任务的结果
std::thread(std::move(task)).detach(); 一个线程中执行 task
std::this_thread::sleep_for 当前线程休眠一段时间,休眠期间不与其他线程竞争CPU,根据线程需求,等待若干时间
std::condition_variable 唤醒等待线程从而避免死锁

std::bind 将实参绑定到调用函数上
std::placeholders::_1 占用符
std::shared_ptr 一种智能指针,它能够记录多少个 shared_ptr 共同指向一个对象
std::make_shared 分配创建传入参数中的对象,并返回这个对象类型的std::shared_ptr指针
std::move 将自己的参数转换为右值
std::forward 会把参数被绑定到一个右值的时候将其转化为右值
std::result_of 在编译的时候推导出一个函数调用表达式的返回值类型

 看一个操作系统中生产者与消费者问题

假设存在一个缓冲区,生产者往里面存数据,消费者从里面取数据,如果缓冲区满了,生产者就不能再往里面添加数据。如果缓冲区没有数据,消费者不能从里面取

下面利用c++11来写一个简单的模型,要理解多线程并发,一个程序可能由多个线程来执行,因此程序上的顺序并不同于多线程中的执行顺序,代码中先写5个生产者,再写5个消费者,但是线程中的顺序并不是这样,可能先执行一个生产者,再执行一个消费者。

#include <condition_variable>
#include <mutex>
#include <thread>
#include <iostream>
#include <queue>
#include <chrono>
int main()
{
// 生产者数量
std::queue<int> produced_nums;
// 互斥锁
std::mutex m;
// 条件变量
std::condition_variable cond_var;
// 结束标志
bool done = false;
// 通知标志
bool notified = false;

// 生产者线程
std::thread producer([&]() {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(1));//当前线程休眠
// 创建互斥锁
std::unique_lock<std::mutex> lock(m);
std::cout << "producing " << i << '\n';
produced_nums.push(i);
notified = true;
// 通知一个线程
cond_var.notify_one();
}
done = true;//生产结束
cond_var.notify_one();//通知休眠的线程执行,完成所有的进程
});

// 消费者线程
std::thread consumer([&]() {
std::unique_lock<std::mutex> lock(m);
while (!done) {
while (!notified) { // 循环避免虚假唤醒,执行完生产者的进程,notified为true,否则停掉消费者线程
cond_var.wait(lock);//停掉当前线程

}
while (!produced_nums.empty()) {
std::cout << "consuming " << produced_nums.front() << '\n';
produced_nums.pop();
}
notified = false;
}
});
producer.join();
consumer.join();
}

 程序的结果是生产一个,消费一个,这样就引发思考了,为什么不可能是生产几个,后消费.发现在生产线程中,先让当前线程休眠,所以生产一个,消费一个。

 线程池类代码

#ifndef ThreadPool_hpp
#define ThreadPool_hpp
#include <vector>               // std::vector
#include <queue>                // std::queue
#include <memory>               // std::make_shared
#include <stdexcept>            // std::runtime_error
#include <thread>               // std::thread
#include <mutex>                // std::mutex,        std::unique_lock
#include <condition_variable>   // std::condition_variable
#include <future>               // std::future,       std::packaged_task
#include <functional>           // std::function,     std::bind
#include <utility>              // std::move,         std::forward

class ThreadPool {
public:
    inline ThreadPool(size_t threads) : stop(false) { //构造函数,且把stop变量赋值为false
        for(size_t i = 0;i<threads;++i)//创造线程实例
            workers.emplace_back([this] {//使用lambda表达式返回this
                for(;;)
                {
                    std::function<void()> task;//function函数对象类,可调用实体的一种类型安全的包裹
                    {
                        std::unique_lock<std::mutex> lock(this->queue_mutex);//互斥量上锁
                        //std::cout<<"thread"<<std::this_thread::get_id()<<"begin work"<<std::endl;
                        this->condition.wait(lock,[this]{ return this->stop || !this->tasks.empty(); });//如果线程池没有销毁且任务队列为空,返回false,该线程休眠
                        if(this->stop && this->tasks.empty())//线程池销毁且任务队列为空,返回
                            return;
                        task = std::move(this->tasks.front());
                        this->tasks.pop();

                    }
                    task();//执行任务
                }

                std::cout<<"thread"<<std::this_thread::get_id()<<"begin work"<<std::endl;
            });


    }
    inline ~ThreadPool() {
        {
            //std::unique_lock<std::mutex> lock(queue_mutex);//互斥量上锁,避免
            stop = true;
        }
        condition.notify_all();//通知所有的休眠线程
        for(std::thread &worker: workers)
            worker.join();
    }
    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args)//可变参数的模板,Args 是一个模板参数包。而在后面的函数参数表中,args 则是函数参数包,用来表示零个或多个参数。
    -> std::future<typename std::result_of<F(Args...)>::type> {
        using return_type = typename std::result_of<F(Args...)>::type;//获取函数返回类型
        auto task = std::make_shared< std::packaged_task<return_type()>>(
                std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
        std::future<return_type> res = task->get_future();//获得 std::future 对象以供实施线程同步
        {
            std::unique_lock<std::mutex> lock(queue_mutex);

            if(stop)
                throw std::runtime_error("enqueue on stopped ThreadPool");
            tasks.emplace([task]{ (*task)(); });//把任务加入队列
        }
        condition.notify_one();//唤醒一个休眠的线程
        return res;
    }
private:
    std::vector<std::thread> workers;//线程池
    std::queue<std::function<void()>> tasks;//任务队列
    std::mutex queue_mutex;//互斥量
    std::condition_variable condition;//条件变量
    bool stop;//结束标志
};
#endif /* ThreadPool_hpp */

测试代码

#include <iostream> // std::cout, std::endl
#include <vector>   // std::vector
#include <string>   // std::string
#include <future>   // std::future
#include <thread>   // std::this_thread::sleep_for
#include <chrono>   // std::chrono::seconds
#include "ThreadPool.h"

int main()
{


    // 创建一个能够并发执行四个线程的线程池
    //std::cout<<std::this_thread::get_id()<<std::endl;
    ThreadPool pool(4);
    // 创建并发执行线程的结果列表

    std::vector< std::future<std::string> > results;

    // 启动八个需要执行的线程任务
    for(int i = 0; i < 8; ++i) {
        // 将并发执行任务的返回值添加到结果列表中
        results.emplace_back(
                // 将下面的打印任务添加到线程池中并发执行
                pool.enqueue([i] {
                    std::cout << "hello " << i << std::endl;
                    // 上一行输出后, 该线程会等待1秒钟
                   std::this_thread::sleep_for(std::chrono::seconds(1));
                    // 然后再继续输出并返回执行情况
                    std::cout << "world " << i << std::endl;

                    return std::string("---thread ") + std::to_string(i) + std::string(" finished.---");
                })
        );
    }

    // 输出线程任务的结果
    for(auto && result: results)
        std::cout << result.get() << '\n';
    std::cout << std::endl;


    return 0;
}

 

posted @ 2019-02-17 14:43  电竞毒奶  阅读(1308)  评论(0编辑  收藏  举报