需要完善的地方 链接到标题

Day10 中,我们添加了一个简单的线程池,一个完整的 Reactor 模型已经成型。但这个线程池存在的问题还比较多,例如任务队列的取出、添加都存在拷贝,性能较差,只能用于学习。

正确操作应该使用右值移动、完美转发等来阻止拷贝。

另外,线程池只能接受 std::function<void()> 类型的函数,所以函数需要使用 Lambda 表达式来创建,或者 std::bind(),且无法得到返回值。

利用模板 链接到标题

class ThreadPool {
  private:
    std::vector<std::thread> threads_;
    std::queue<std::function<void()>> tasks_;
    std::mutex tasks_mtx_;
    std::condition_variable cv_;
    bool stop_;

  public:
    explicit ThreadPool(int size = 8);
    ~ThreadPool();

    template <class F, class... Args>
    auto add_task(F &&f, Args &&...args) -> std::future<typename std::result_of<F(Args...)>::type>;
};

template <class F, class... Args>
auto ThreadPool::add_task(F &&f, Args &&...args) -> std::future<typename std::result_of<F(Args...)>::type> {
    using return_type = typename std::result_of<F(Args...)>::type;
    auto ptask = std::make_shared<std::packaged_task<return_type()>>(
        std::bind(std::forward<F>(f), std::forward<Args>(args)...));
    /* auto ptask = std::make_shared<std::packaged_task<return_type()>>([f = std::forward<F>(f), args = std::make_tuple(std::forward<Args>(args)...)]() mutable { */
    /*     return std::apply(std::move(f), std::move(args)); */
    /* }); */
    std::future<return_type> res = ptask->get_future();
    {
        std::unique_lock<std::mutex> lock(tasks_mtx_);
        // don't allow enqueueing after stopping the pool
        if (stop_) {
            throw std::runtime_error("enqueue on stopped ThreadPool");
        }
        tasks_.emplace([ptask]() { (*ptask)(); });
    }
    cv_.notify_one();
    return res;
}

代码说明 链接到标题

在函数声明的部分有

template <class F, class... Args>
auto add_task(F &&f, Args &&...args) -> std::future<typename std::result_of<F(Args...)>::type>;

这里谈谈我个人的一点理解,首先,是 template<class F, class... Args>,这是函数模板,表示函数可以接受两种任意类型的参数,一个参数类型是暂定是 F,到编译阶段才会被推导出来。另一个参数则是可变模板参数,你可以理解为它可以接受多个不同类型的参数。

因此,单看 template<class F, class... Args>,似乎和 template<class... Args> 没有区别。

然而,后面的 std::future<typename std::result_of<F(Args...)>::type> 则限定了 F 的类型一定是 callable 类型,后面简称函数类型。

void EventLoop::add_thread(std::function<void()> func) {
    thread_pool_->add_task(func);
}

结合 add_task 的函数声明的要求,我们在通过调用 add_task 来实现 add_thread 的时候,只要求传递给 add_task 的第一个参数,必须是可调用类型,即 func,后面的参数可以再自行决定。

std::future<typename std::result_of<F(Args...)>::type> 使用了 std::future 库,typename 用于指示编译器 std::result_of<F(Args...)>::type 是一个类型的关键字。

std::result_of 是一个类型推导表达式。用于确定调用类型为 F 的可调用对象(比如函数、函数指针、Lambda等)时,使用参数类型为 Args… 的参数列表所返回的类型。

std::future<typename std::result_of<F(Args...)>::type> 通常用于异步编程中提交任务并且获取结果。

using return_type = typename std::result_of<F(Args...)>::type;
auto ptask = std::make_shared<std::packaged_task<return_type()>>(
    std::bind(std::forward<F>(f), std::forward<Args>(args)...));

注意 std::make_shared<T>(x) 中,x 是用来构造 T 类型的对象的参数。

std::packaged_task 是 C++ 标准库中的一个模板类,它表示可以异步执行的任务,并返回一个值。

std::packaged_task<return_type()>`` 中 return_type`` 表示的是一个类型,为什么后面还跟着一个括号? 这里的括号是 C++ 中用于表示返回值类型的语法,<return_type()> 表示将 return_type 视为一个类型,表示 std::packaged_task 包装的任务将返回这个类型的值。

例如:

std::packaged_task<int()> task([]() {
    return 42;
}); // 希望 std::packaged_task 包装的任务返回一个整数类型的结果

添加测试程序 链接到标题

简单来说就是创建 n 个线程,每个线程发送接受相同的消息 k 次。

./test -t 10000 -m 10 -w 5

Acceptor 的一点改进 链接到标题

对于 Acceptor,接受连接的处理时间短、报文数据小,并且同一时间一般不会有特别多的新连接到来。所以 Acceptor 没必要采用 ET 模式,也没有必要采用线程池。

即然 Acceptor 不会成为性能瓶颈,那么最好使用阻塞式 socket。

所以,day11 的源码中做了以下改变:

  1. Acceptor 的 serv_sock 采用不设置成非阻塞;
  2. Acceptor 使用 LT 模式,Connection 使用 ET;
  3. Acceptor 建立连接不使用线程池,建立好连接之后,处理事件使用线程池;