需要完善的地方 链接到标题
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 的源码中做了以下改变:
- Acceptor 的 serv_sock 采用不设置成非阻塞;
- Acceptor 使用 LT 模式,Connection 使用 ET;
- Acceptor 建立连接不使用线程池,建立好连接之后,处理事件使用线程池;