一. Asio是一个异步库及其原理
异步,即线程提交io请求后直接返回,事件完成后再进行通知。在不同的操作系统中有不同的事件监听机制,linux采用epoll,windows采用iocp,unix采用kqueue。Asio使用前摄器模式(proactor),通过宏来实现在各个不同的平台上调用不同的底层实现。
注:(以下对于asio相关描述均基于linux系统)
Asio只提供一种异步的机制,内部维护队列op_queue(线程安全),符合生产者消费者模型。
一年前写的了,不知不觉过了这么久,想了想还是发出来,算是自己曾经阅读的痕迹。
1.1 类图
以下为io_context相关类的类图,后续会继续补充。
注:以上类图中service为excution_context内部类
1.2 proactor/reactor
linux采用rector模型,假如Linux下对多个文件句柄进行监控,当有事件触发时,会通知监听者,告知数据来了,让业务可以去进行处理。
win采用proactor模型,当有数据来了,会通过回调函数自行对数据进行处理,处理完后再进行通知。
二. Asio中service、post和run
void print_a(){
printf(“a\n”);
}
void print_b(){
printf(“b\n”);
}
int main()
{
asio::io_context io_context;
asio::post(io_context, print_a);
io_context.run();
asio::post(io_context, print_b);
io_context.restart();
io_context.run();
return 0;
}
下面根据代码逻辑进行讲解。
2.1 service
使用Asio必须要定义asio::io_context,一个io_context对应一个 service_registry ,service_registry维护了一个由service组成的链表,其用于管理当前io_context的所有service。
io_context该类的实现是scheduler,是service子类之一。当定义io_context时,会执行add_service,将scheduler加入到service链表中。
引申:当使用一个service时,会调用 use_service 去获取指定type的service对象,use_service内部实现是:会检查当前service链表中是否存在要使用的service,如果有,则直接使用,没有则将service添加进链表中。这里能分辨出不同service的原因是由于每个service内部有一个key,key的组成为id和typeinfo。
struct key
{
key() : type_info_(0), id_(0) {}
const std::type_info* type_info_;
const execution_context::id* id_;
} key_;
2.2 post
post操作会将函数指针放入scheduler类中的op_queue。
post最终会调用post_immediate_completion函数。
(在此之前的逻辑为将函数指针封装成executor_op类型,不是此处重点,略过。)
io_context_->impl_.post_immediate_completion(p.p,
(bits_ & relationship_continuation) != 0);
接下来看post_immediate_completion,为以下代码段。
重点:注意代码后几句向op_queue进行了push操作,并且执行work_started函数,其内部会将成员变量 outstanding_work_++,即未完成工作数量加一。
这里if中的内容,我的理解是一个延迟处理的情况。最外层if当传参选项为relationship_continuation可以进入。在当前例子中,选项为blocking_never,无法进入。里层if这里判断了当前线程是否处于调用栈thread_call_stack中。(提前说一下,后面可以看到,在run时会将this_thread放入调用栈;在run结束,析构ctx时会将信息移出栈,此处是使用top指向next,如下)
~context()
{
call_stack<Key, Value>::top_ = next_;
}
即处于调用栈中,则代表 正在执行run函数 。此时会将op函数指针放入private_op_queue私有队列中,对应的私有队列未完成工作数加一,后续会从私有队列中去取,再加入到op_queue中 。
void scheduler::post_immediate_completion(
scheduler::operation* op, bool is_continuation)
{
#if defined(ASIO_HAS_THREADS)
if (one_thread_ || is_continuation)
{
if (thread_info_base* this_thread = thread_call_stack::contains(this))
{
++static_cast<thread_info*>(this_thread)->private_outstanding_work;
static_cast<thread_info*>(this_thread)->private_op_queue.push(op);
return;
}
}
#else // defined(ASIO_HAS_THREADS)
(void)is_continuation;
#endif // defined(ASIO_HAS_THREADS)
work_started();
mutex::scoped_lock lock(mutex_);
op_queue_.push(op);
wake_one_thread_and_unlock(lock);
}
2.3 run
run操作会去scheduler类中的op_queue中取函数指针。
以下为scheduler中run的代码。其中需要注意的是,成员变量 outstanding_work_ ,该变量记录未完成的工作,即还没有被执行的函数指针。当outstanding_work_为0时,scheduler类成员 stopped_ 被置为true,表示scheduler中一些操作将不能被执行(post内部可以执行,do_run_one内部不会被执行)。所以,当run后,需要使用 restart 将stopped_置为flase。
注:这里说一下 stopped_ 被置为true的几种情况
1.work_finished() :该函数会-- outstanding_work_,为0时,stopped_被 置为true。
eg. do_run_one do_poll_one do_wait_one 通过work_cleanup结构体执行work_finished
2.run()
3.scheduler类析构
4.shutdown
然后在代码中,我们可以看到上述所说, 将this_thread放入调用栈thread_call_stack的代码。
std::size_t scheduler::run(asio::error_code& ec)
{
ec = asio::error_code();
if (outstanding_work_ == 0)
{
stop();
return 0;
}
thread_info this_thread;
this_thread.private_outstanding_work = 0;
thread_call_stack::context ctx(this, this_thread);
mutex::scoped_lock lock(mutex_);
std::size_t n = 0;
for (; do_run_one(lock, this_thread, ec); lock.lock())
if (n != (std::numeric_limitsstd::size_t::max)())
++n;
return n;
}
run中会调用do_run_one,以下为代码段。
(这里主要是讲o != &task_operation_时,其余部分后续用到时再讲。)
work_cleanup结构体有一个析构函数,内部会判断,当没有未完成工作时,将 stopped_ 置为true。
执行 complete 处理任务,执行函数指针。
——这里涉及asio将函数指针封装成executor_op类型,asio对不同的函数指针封装成了不同的op,比如还有wait_op,reactor_op等,其均继承自Operation,感兴趣的话可以自行了解。
std::size_t scheduler::do_run_one(mutex::scoped_lock& lock,
scheduler::thread_info& this_thread,
const asio::error_code& ec)
{
while (!stopped_)
{
if (!op_queue_.empty())
{
// Prepare to execute first handler from queue.
operation* o = op_queue_.front();
op_queue_.pop();
bool more_handlers = (!op_queue_.empty());
if (o == &task_operation_)
{
task_interrupted_ = more_handlers;
if (more_handlers && !one_thread_)
wakeup_event_.unlock_and_signal_one(lock);
else
lock.unlock();
task_cleanup on_exit = { this, &lock, &this_thread };
(void)on_exit;
// Run the task. May throw an exception. Only block if the operation
// queue is empty and we're not polling, otherwise we want to return
// as soon as possible.
task_->run(more_handlers ? 0 : -1, this_thread.private_op_queue);
}
else
{
std::size_t task_result = o->task_result_;
if (more_handlers && !one_thread_)
wake_one_thread_and_unlock(lock);
else
lock.unlock();
// Ensure the count of outstanding work is decremented on block exit.
work_cleanup on_exit = { this, &lock, &this_thread };
(void)on_exit;
// Complete the operation. May throw an exception. Deletes the object.
o->complete(this, ec, task_result);
this_thread.rethrow_pending_exception();
return 1;
}
}
else
{
wakeup_event_.clear(lock);
wakeup_event_.wait(lock);
}
}
return 0;
}
三. Asio的其他异步实现
3.1 定时器deadline_timer
void print_a(asio::error_code ec)
{
std::cout << “hello” << std::endl;
}
void print_a1(asio::error_code ec)
{
std::cout << “world” << std::endl;
}
int main()
{
asio::io_context io;
asio::deadline_timer t(io, boost::posix_time::seconds(5));
t.async_wait(print_a);
t.async_wait(print_a1);
io.run();
return 0;
}
deadline_timer第一个参数为io_context,第二个参数为等待时间。
定义时,在内部会用expires_from_now计算出终止时间。
3.1.1 deadline_timer
deadline_timer成员变量impl:
detail::io_object_impl<detail::deadline_timer_service, Executor> impl_;
关于上面的 io_object_impl 类,这里讲解其中的两个成员变量:
成员变量service; 通过use_service获取,为deadline_timer的服务类deadline_timer_service。
成员变量implementation_; 为结构体 implementation_type 类型。其中需要注意的是结构体implementation_type,每个service类中都有对应的该结构体的实现。在当前例子中,该结构体主要用于存储与deadline_timer变量一一对应的数据,即存储数据对象。
deadline_timer_service中的implementation_type:
-
struct implementation_type
-
private asio::detail::noncopyable
{
time_type expiry;
bool might_have_pending_waits;
typename timer_queue<Time_Traits>::per_timer_data timer_data;
};
以下话题为 deadline_timer_service 相关。
deadline_timer_service成员变量scheduler_;为 epoll_reactor 类型,该类也是一个服务类。其内部创建了timer_fd和epoll_fd,并通过epoll_fd对timer_fd进行了事件的监听。
epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, timer_fd_, &ev);
deadline_timer_service的构造函数主要做的事情:
op_queue_.push(&task_operation_); (task_operation_主要是一个空结构体,用于在op_queue中占位)
add_timer_queue(由于asio内部有多种定时器,所以存在维护定时器的链表,此处暂不详说)
3.1.2 wait同步调用
deadline_timer_service中的wait方法是使用select进行阻塞等待。
3.1.3 async_wait 异步调用
deadline_timer_service是服务类,内部成员变量timer_queue_维护多个deadline_timer类型的定时器。
// The queue of timers.
timer_queue<Time_Traits> timer_queue_;
timer_queue类内部维护一个vector,元素类型为结构体heap_entry。
struct heap_entry
{
// The time when the timer should fire.
time_type time_;
// The associated timer with enqueued operations.
per_timer_data* timer_;
};
// The heap of timers, with the earliest timer at the front.
std::vector<heap_entry> heap_;
这里将终止时间time与per_timer_data进行绑定,per_timer_data内部存在队列op_queue维护了多个操作(这里是函数指针)。对vector使用最小堆按时间进行排序。
由于deadline_timer_service是服务类,所以对于同一种计时器,vector全局唯一。
per_timer_data为timer_queue的内部类,可以看到内部维护了在当前timer上的op_queue_,其中还有堆的索引,上一个节点prev_以及下一节点_next。
class per_timer_data
{
public:
per_timer_data() :
heap_index_((std::numeric_limitsstd::size_t::max)()),
next_(0), prev_(0)
{
}
private:
friend class timer_queue;
// The operations waiting on the timer.
op_queue<wait_op> op_queue_;
// The index of the timer in the heap.
std::size_t heap_index_;
// Pointers to adjacent timers in a linked list.
per_timer_data* next_;
per_timer_data* prev_;
};
async_wait最终会在sevice中调用epoll_reactor类的成员函数scheduler_timer。
该函数内部,检查计时器已关闭,则将操作直接进行post操作,及直接将op放到op_queue中。
重点:enqueue_timer操作
1.将终止时间及对应per_timer_data插入到堆中
2.堆排
3.将对应的函数指针op插入到per_timer_data中的op_中
template
void epoll_reactor::schedule_timer(timer_queue<Time_Traits>& queue,
const typename Time_Traits::time_type& time,
typename timer_queue<Time_Traits>::per_timer_data& timer, wait_op* op)
{
mutex::scoped_lock lock(mutex_);
if (shutdown_)
{
scheduler_.post_immediate_completion(op, false);
return;
}
bool earliest = queue.enqueue_timer(time, timer, op);
scheduler_.work_started();
if (earliest)
update_timeout();
}
以下为enqueue_timer,up_heap进行最小堆排序。
// Add a new timer to the queue. Returns true if this is the timer that is
// earliest in the queue, in which case the reactor’s event demultiplexing
// function call may need to be interrupted and restarted.
bool enqueue_timer(const time_type& time, per_timer_data& timer, wait_op* op)
{
// Enqueue the timer object.
if (timer.prev_ == 0 && &timer != timers_)
{
if (this->is_positive_infinity(time))
{
// No heap entry is required for timers that never expire.
timer.heap_index_ = (std::numeric_limitsstd::size_t::max)();
}
else
{
// Put the new timer at the correct position in the heap. This is done
// first since push_back() can throw due to allocation failure.
timer.heap_index_ = heap_.size();
heap_entry entry = { time, &timer };
heap_.push_back(entry);
up_heap(heap_.size() - 1);
}
// Insert the new timer into the linked list of active timers.
timer.next_ = timers_;
timer.prev_ = 0;
if (timers_)
timers_->prev_ = &timer;
timers_ = &timer;
}
// Enqueue the individual timer operation.
timer.op_queue_.push(op);
// Interrupt reactor only if newly added timer is first to expire.
return timer.heap_index_ == 0 && timer.op_queue_.front() == op;
}
heap_.push_back(entry);每次将节点插入到最小堆的尾部,up_heap进行排序:此时前面节点均为有序,不断将当前节点与其父节点比较,小的话则将换位置,维护最小堆。
// Move the item at the given index up the heap to its correct position.
void up_heap(std::size_t index)
{
while (index > 0)
{
std::size_t parent = (index - 1) / 2;
if (!Time_Traits::less_than(heap_[index].time_, heap_[parent].time_))
break;
swap_heap(index, parent);
index = parent;
}
}
3.1.4 run操作
此处的run会涉及o == &task_operation_时,前面我们说过task_operation_为空结构,用于占位。
if (o == &task_operation_)
{
task_interrupted_ = more_handlers;
if (more_handlers && !one_thread_)
wakeup_event_.unlock_and_signal_one(lock);
else
lock.unlock();
task_cleanup on_exit = { this, &lock, &this_thread };
(void)on_exit;
// Run the task. May throw an exception. Only block if the operation
// queue is empty and we're not polling, otherwise we want to return
// as soon as possible.
task_->run(more_handlers ? 0 : -1, this_thread.private_op_queue);
}
最后一句执行epoll_reactor中的run,将私有队列传入。前面我们说到 epoll_ctl通过epoll_fd对timer_fd进行了事件的监听, 其中这里的run执行epoll_wait将上述监听到的事件拿出。
// Block on the epoll descriptor.
epoll_event events[128];
int num_events = epoll_wait(epoll_fd_, events, 128, timeout);
如果 存在相应的定时器事件,则调用get_ready_timers,其内部将过期时间到了的事件取出,放入private_op_queue私有队列中。
// Dequeue all timers not later than the current time.
virtual void get_ready_timers(op_queue& ops)
{
if (!heap_.empty())
{
const time_type now = Time_Traits::now();
while (!heap_.empty() && !Time_Traits::less_than(now, heap_[0].time_))
{
per_timer_data* timer = heap_[0].timer_;
ops.push(timer->op_queue_);
remove_timer(*timer);
}
}
}
当函数结束时,其中task_cleanup进行析构:
1.更新outstanding_work_,加上private_outstanding_work, 并private_outstanding_work = 0
2.将私有队列放入op_queue,并在op_queue末尾放入task_operation_。
前面说到在deadline_timer_service的构造函数中进行了放task_operation_的操作。
注:这里我们可以发现, task_operation_的作用 其实就是
1.调用epoll_wait去拿事件,放入私有队列
2.将私有队列内容放入op_queue
struct scheduler::task_cleanup
{
~task_cleanup()
{
if (this_thread_->private_outstanding_work > 0)
{
asio::detail::increment(
scheduler_->outstanding_work_,
this_thread_->private_outstanding_work);
}
this_thread_->private_outstanding_work = 0;
// Enqueue the completed operations and reinsert the task at the end of
// the operation queue.
lock_->lock();
scheduler_->task_interrupted_ = true;
scheduler_->op_queue_.push(this_thread_->private_op_queue);
scheduler_->op_queue_.push(&scheduler_->task_operation_);
}
scheduler* scheduler_;
mutex::scoped_lock* lock_;
thread_info* this_thread_;
};