asio异步库

一. 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_;
};

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值