boost asio io_service学习笔记
构造函数
构造函数的主要动作就是调用CreateIoCompletionPort创建了一个初始iocp。
Dispatch和post的区别
Post一定是PostQueuedCompletionStatus并且在GetQueuedCompletionStatus 之后执行。
Dispatch会首先检查当前thread是不是io_service.run/runonce/poll/poll_once线程,如果是,则直接运行。
poll和run的区别
两者代码几乎一样,都是首先检查是否有outstanding的消息,如果没有直接返回,否则调用do_one()。唯一的不同是在调用size_t do_one(bool block, boost::system::error_code& ec)时前者block = false,后者block = true。
该参数的作用体现在:
BOOL ok = ::GetQueuedCompletionStatus(iocp_.handle, &bytes_transferred,
&completion_key, &overlapped, block ? timeout : 0);
因此可以看出,poll处理的是已经完成了的消息,也即GetQueuedCompletionStatus立刻能返回的。而run则会导致等待。
poll 的作用是依次处理当前已经完成了的消息,直到所有已经完成的消息处理完成为止。如果没有已经完成了得消息,函数将退出。poll不会等待。这个函数有点类似于PeekMessage。鉴于PeekMessage很少用到,poll的使用场景我也有点疑惑。poll的一个应用场景是如果希望handler的处理有优先级,也即,如果消息完成速度很快,同时可能完成多个消息,而消息的处理过程可能比较耗时,那么可以在完成之后的消息处理函数中不真正处理数据,而是把handler保存在队列中,然后按优先级统一处理。代码如下:
while (io_service.run_one()) {
// The custom invocation hook adds the handlers to the priority queue
// rather than executing them from within the poll_one() call.
while (io_service.poll_one()) ; pri_queue.execute_all(); }
循环执行poll_one让已经完成的消息的wrap_handler处理完毕,也即插入一个队列中,然后再统一处理之。这里的wrap_handler是一个class,在post的时候,用如下代码:
io_service.post(pri_queue.wrap(0, low_priority_handler));或者 acceptor.async_accept(server_socket, pri_queue.wrap(100, high_priority_handler));
template <typename Handler> wrapped_handler<Handler> handler_priority_queue::wrap(int priority, Handler handler)
{ return wrapped_handler<Handler>(*this, priority, handler); }
参见boost_asio/example/invocation/prioritised_handlers.cpp
这个sample也同时表现了wrap的使用场景。
也即把handler以及参数都wrap成一个object,然后把object插入一个队列,在pri_queue.execute_all中按优先级统一处理。
run的作用是处理消息,如果有消息未完成将一直等待到所有消息完成并处理之后才退出。
reset和stop
文档中reset的解释是重置io_service以便下一次调用。
当 run,run_one,poll,poll_one是被stop掉导致退出,或者由于完成了所有任务(正常退出)导致退出时,在调用下一次 run,run_one,poll,poll_one之前,必须调用此函数。reset不能在run,run_one,poll,poll_one正在运行时调用。如果是消息处理handler(用户代码)抛出异常,则可以在处理之后直接继续调用 io.run,run_one,poll,poll_one。 例如:
boost::asio::io_service io_service;
...
for (;;)
{
try
{
io_service.run();
break; // run() exited normally
}
catch (my_exception& e)
{
// Deal with exception as appropriate.
}
}在抛出了异常的情况下,stopped_还没来得及被asio设置为1,所以无需调用reset。
reset函数的代码仅有一行:
void reset()
{
::InterlockedExchange(&stopped_, 0);
}
也即,当io.stop时,会设置stopped_=1。当完成所有任务时,也会设置。
总的来说,单线程情况下,不管io.run是如何退出的,在下一次调用io.run之前调用一次reset没有什么坏处。例如:
for(;;)
{
try
{
io.run();
}
catch(…)
{
}
io.reset();
}
如果是多线程在运行io.run,则应该小心,因为reset必须是所有的run,run_one,poll,poll_one退出后才能调用。
文档中的stop的解释是停止io_service的处理循环。
此函数不是阻塞函数,也即,它仅仅只是给iocp发送一个退出消息而并不是等待其真正退出。因为poll和poll_one本来就不等待(GetQueuedCompletionStatus时timeout = 0),所以此函数对poll和poll_one无意义。对于run_one来说,如果该事件还未完成,则run_one会立刻返回。如果该事件已经完成,并且还在处理中,则stop并无特殊意义(会等待handler完成后自然退出)。对于run来说,stop的调用会导致run中的 GetQueuedCompletionStatus立刻返回。并且由于设置了stopped = 1,此前完成的消息的handlers也不会被调用。考虑一下这种情况:在io.stop之前,有1k个消息已经完成但尚未处理,io.run正在依次从 GetQueuedCompletionStatus中获得信息并且调用handlers,调用io.stop设置stopped=1将导致后许 GetQueuedCompletionStatus返回的消息直接被丢弃,直到收到退出消息并退出io.run为止。
void stop()
{
if (::InterlockedExchange(&stopped_, 1) == 0)
{
if (!::PostQueuedCompletionStatus(iocp_.handle, 0, 0, 0))
{
DWORD last_error = ::GetLastError();
boost::system::system_error e(
boost::system::error_code(last_error,
boost::asio::error::get_system_category()),
"pqcs");
boost::throw_exception(e);
}
}
}
注意除了让当前代码退出之外还有一个副作用就是设置了stopped_=1。这个副作用导致在stop之后如果不调用reset,所有run,run_one,poll,poll_one都将直接退出。
另一个需要注意的是,stop会导致所有未完成的消息以及完成了但尚未处理得消息都直接被丢弃,不会导致handlers倍调用。
注意这两个函数都不会CloseHandle(iocp.handle_),那是析构函数干的事情。
注意此处有个细节:一次PostQueuedCompletionStatus仅导致一次 GetQueuedCompletionStatus返回,那么如果有多个thread此时都在io.run,并且block在 GetQueuedCompletionStatus时,调用io.stop将PostQueuedCompletionStatus并且导致一个 thread的GetQueuedCompletionStatus返回。那么其他的thread呢?进入io_service的do_one(由run 函数调用)代码可以看到,当GetQueuedCompletionStatus返回并且发现是退出消息时,会再发送一次 PostQueuedCompletionStatus。代码如下:
else
{
// Relinquish responsibility for dispatching timers. If the io_service
// is not being stopped then the thread will get an opportunity to
// reacquire timer responsibility on the next loop iteration.
if (dispatching_timers)
{
::InterlockedCompareExchange(&timer_thread_, 0, this_thread_id);
}
// The stopped_ flag is always checked to ensure that any leftover
// interrupts from a previous run invocation are ignored.
if (::InterlockedExchangeAdd(&stopped_, 0) != 0)
{
// Wake up next thread that is blocked on GetQueuedCompletionStatus.
if (!::PostQueuedCompletionStatus(iocp_.handle, 0, 0, 0))
{
last_error = ::GetLastError();
ec = boost::system::error_code(last_error,
boost::asio::error::get_system_category());
return 0;
}
ec = boost::system::error_code();
return 0;
}
}
}
Wrap
这个函数是一个语法糖。
Void func(int a);
io_service.wrap(func)(a);
相当于io_service.dispatch(bind(func,a));
可以保存io_service.wrap(func)到g,以便在稍后某些时候调用g(a);
例如:
socket_.async_read_some(boost::asio::buffer(buffer_), strand_.wrap(
boost::bind(&connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
这是一个典型的wrap用法。注意async_read_some要求的参数是一个handler,在read_some结束后被调用。由于希望真正被调用的handle_read是串行化的,在这里再post一个消息给io_service。以上代码类似于:
void A::func(error,bytes_transferred)
{
strand_.dispatch(boost::bind(handle_read,shared_from_this(),error,bytes_transferred);
}
socket_.async_read_some(boost::asio::buffer(buffer_), func);
注意1点:
io_service.dispatch(bind(func,a1,…an)),这里面都是传值,无法指定bind(func,ref(a1)…an)); 所以如果要用ref语义,则应该在传入wrap时显式指出。例如:
void func(int& i){i+=1;}
void main()
{
int i = 0;
boost::asio::io_service io;
io.wrap(func)(boost::ref(i));
io.run();
printf("i=%d\n");
}
当然在某些场合下,传递shared_ptr也是可以的(也许更好)。
从handlers抛出的异常的影响
当handlers抛出异常时,该异常会传递到本线程最外层的io.run,run_one,poll,poll_one,不会影响其他线程。捕获该异常是程序员自己的责任。
例如:
boost::asio::io_service io_service;
Thread1,2,3,4()
{
for (;;)
{
try
{
io_service.run();
break; // run() exited normally
}
catch (my_exception& e)
{
// Deal with exception as appropriate.
}
}
}
Void func(void)
{
throw 1;
}
Thread5()
{
io_service.post(func);
}
注意这种情况下无需调用io_service.reset()。
这种情况下也不能调用reset,因为调用reset之前必须让所有其他线程正在调用的io_service.run退出。(reset调用时不能有任何run,run_one,poll,poll_one正在运行)
Work
有些应用程序希望在没有pending的消息时,io.run也不退出。比如io.run运行于一个后台线程,该线程在程序的异步请求发出之前就启动了。
可以通过如下代码实现这种需求:
main()
{
boost::asio::io_service io_service;
boost::asio::io_service::work work(io_service);
Create thread
Getchar();
}
Thread()
{
Io_service.run();
}
这种情况下,如果work不被析构,该线程永远不会退出。在work不被析构得情况下就让其退出,可以调用io.stop。这将导致 io.run立刻退出,所有未完成的消息都将丢弃。已完成的消息(但尚未进入handler的)也不会调用其handler函数(由于在stop中设置了 stopped_= 1)。
如果希望所有发出的异步消息都正常处理之后io.run正常退出,work对象必须析构,或者显式的删除。
boost::asio::io_service io_service;
auto_ptr<boost::asio::io_service::work> work(
new boost::asio::io_service::work(io_service));
...
work.reset(); // Allow run() to normal exit.
work是一个很小的辅助类,只支持构造函数和析构函数。(还有一个get_io_service返回所关联的io_service)
代码如下:
inline io_service::work::work(boost::asio::io_service& io_service)
: io_service_(io_service)
{
io_service_.impl_.work_started();
}
inline io_service::work::work(const work& other)
: io_service_(other.io_service_)
{
io_service_.impl_.work_started();
}
inline io_service::work::~work()
{
io_service_.impl_.work_finished();
}
void work_started()
{
::InterlockedIncrement(&outstanding_work_);
}
// Notify that some work has finished.
void work_finished()
{
if (::InterlockedDecrement(&outstanding_work_) == 0)
stop();
}
可以看出构造一个work时,outstanding_work_+1,使得io.run在完成所有异步消息后判断outstanding_work_时不会为0,因此会继续调用GetQueuedCompletionStatus并阻塞在这个函数上。
而析构函数中将其-1,并判断其是否为0,如果是,则post退出消息给GetQueuedCompletionStatus让其退出。
因此work如果析构,则io.run会在处理完所有消息之后正常退出。work如果不析构,则io.run会一直运行不退出。如果用户直接调用io.stop,则会让io.run立刻退出。
特别注意的是,work提供了一个拷贝构造函数,因此可以直接在任意地方使用。对于一个io_service来说,有多少个work实例关联,则outstanding_work_就+1了多少次,只有关联到同一个io_service的work全被析构之后,io.run才会在所有消息处理结束之后正常退出。
strand
strand是另一个辅助类,提供2个接口dispatch和post,语义和io_service的dispatch和post类似。区别在于,同一个strand所发出的dispatch和post绝对不会并行执行,dispatch和post所包含的handlers也不会并行。因此如果希望串行处理每一个tcp连接,则在accept之后应该在该连接的数据结构中构造一个strand,并且所有dispatch/post(recv /send)操作都由该strand发出。strand的作用巨大,考虑如下场景:有多个thread都在执行async_read_some,那么由于线程调度,很有可能后接收到的包先被处理,为了避免这种情况,就只能收完数据后放入一个队列中,然后由另一个线程去统一处理。
void connection::start()
{
socket_.async_read_some(boost::asio::buffer(buffer_),
strand_.wrap(
boost::bind(&connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
不使用strand的处理方式:
前端tcp iocp收包,并且把同一个tcp连接的包放入一个list,如果list以前为空,则post一个消息给后端vnn iocp。后端vnn iocp收到post的消息后循环从list中获取数据,并且处理,直到list为空为止。处理结束后重新调用 GetQueuedCompletionStatus进入等待。如果前端tcp iocp发现list过大,意味着处理速度小于接收速度,则不再调用iocpRecv,并且设置标志,当vnn iocp thread处理完了当前所有积压的数据包后,检查这个标志,重新调用一次iocpRecv。
使用strand的处理方式:
前端tcp iocp收包,收到包后直接通过strand.post(on_recved)发给后端vnn iocp。后端vnn iocp处理完之后再调用一次strand.async_read_some。
这两种方式我没看出太大区别来。如果对数据包的处理的确需要阻塞操作,例如db query,那么使用后端iocp以及后端thread是值得考虑的。这种情况下,前端iocp由于仅用来异步收发数据,因此1个thread就够了。在确定使用2级iocp的情况下,前者似乎更为灵活,也没有增加什么开销。
值得讨论的是,如果后端多个thread都处于db query状态,那么实际上此时依然没有thread可以提供数据处理服务,因此2级iocp意义其实就在于在这种情况下,前端tcp iocp依然可以accept,以及recv第一次数据,不会导致用户connect不上的情况。在后端thread空闲之后会处理这期间的recv到的数据并在此async_read_some。
如果是单级iocp(假定handlers没有阻塞操作),多线程,那么strand的作用很明显。这种情况下,很明显应该让一个tcp连接的数据处理过程串行化。
Strand的实现原理
Strand内部实现机制稍微有点复杂。每次发出strand请求(例如 async_read(strand_.wrap(funobj1))),strand再次包裹了一次成为funobj2。在async_read完成时,系统调用funobj2,检查是否正在执行该strand所发出的完成函数(检查该strand的一个标志位),如果没有,则直接调用 funobj2。如果有,则检查是否就是当前thread在执行,如果是,则直接调用funobj2(这种情况可能发生在嵌套调用的时候,但并不产生同步问题,就像同一个thread可以多次进入同一个critical_session一样)。如果不是,则把该funobj2插入到strand内部维护的一个队列中。
boost asio io_service学习笔记的更多相关文章
- boost timer代码学习笔记
socket连接中需要判断超时 所以这几天看了看boost中计时器的文档和示例 一共有五个例子 从简单的同步等待到异步调用超时处理 先看第一个例子 // timer1.cpp: 定义控制台应用程序的入 ...
- boost::asio::io_service::定时器任务队列
使用io_service和定时器写的一个同步和异步方式的任务队列 #pragma once #include <string> #include <iostream> #inc ...
- 概念理解:boost::asio::io_service
IO模型 io_service对象是asio框架中的调度器,所有异步io事件都是通过它来分发处理的(io对象的构造函数中都需要传入一个io_service对象). asio::io_service i ...
- boost::asio::io_service类
大部分使用Boost.Asio编写的代码都会使用几个io_service的实例.io_service是这个库里面最重要的类:它负责和操作系统打交道,等待所有异步操作的结束,然后为每一个异步操作调用其完 ...
- Boost::asio io_service 实现分析
io_service的作用 io_servie 实现了一个任务队列,这里的任务就是void(void)的函数.Io_servie最常用的两个接口是post和run,post向任务队列中投递任务,run ...
- Boost线程库学习笔记
一.创建一个线程 创建线程 boost::thread myThread(threadFun); 需要注意的是:参数可以是函数对象或者函数指针.并且这个函数无参数,并返回void类型. 当一个thre ...
- 初探boost之noncopyable学习笔记
noncopyable 功能 同意程序轻松实现一个不可复制的类. 需包括头文件 #include<boost/noncopyable.hpp> 或 #include<boos ...
- boost asio 学习(一)io_service的基础
原文 http://www.gamedev.net/blog/950/entry-2249317-a-guide-to-getting- started-with-boostasio/ 编译环境 b ...
- BOOST学习笔记
BOOST学习笔记 1 tool #pragma once #include <vector> #include "boost/noncopyable.hpp" #in ...
随机推荐
- ceph源码之一
转自于:http://blog.csdn.net/changtao381/article/details/8698935 一.概述: 其结构如下:在src 里, 网络通信: msg 里面 包括了网 ...
- BZOJ 1263: [SCOI2006]整数划分( 高精度 )
yy一下发现好像越小越好...分解成3*3*3*3……这种形式是最好的...然后就是高精度了 ----------------------------------------------------- ...
- HDU 3974 Assign the task 简单搜索
根据Rex 的思路才知道可以这么写. 题目意思还是很好理解的,就是找到当前雇员最近的任务. 做法是,可以开辟一个 tim 变量,每次有雇员得到昕任务时候 ++tim 然后取寻找最近的任务的时候写一个搜 ...
- Chapter 13 建造者模式
建造者模式又叫生成器模式:将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象. 代码: package xiao; import java.util. ...
- ASP.NET MVC 5 学习教程:数据迁移之添加字段
原文 ASP.NET MVC 5 学习教程:数据迁移之添加字段 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符 ...
- 说服式设计(persuasive design)的行为模型
转自:http://www.sharetk.com/html/ued/User-Research/1404.html 一 模型简介 BJ Fogg提出了一个新的理解人类行为的模型,他称之为Fogg b ...
- servlet 将输入内容通过拼接页面的方式显示出来
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/ ...
- python groupby
groupby() 将key函数作用于原循环器的各个元素.根据key函数结果,将拥有相同函数结果的元素分到一个新的循环器.每个新的循环器以函数返回结果为标签. 这就好像一群人的身高作为循环器.我们可以 ...
- 微软新一代输入法框架 TSF - Text Service Framework 小小的研究
实际上windows中有两套输入法框架,一套叫做imm32.一套叫做tsf,win7以后的新系统都是优先使用tsf的,现在新出的输入法基本也是基于tsf的. 你可以参考一下这篇文章,虽然是c++的代码 ...
- shell登录模式及其相应配置文件(转)
参考<linux命令.编辑器与shell编程>(清华大学出版社) 当启动shell时,它将运行启动文件来初始化自己.具体运行哪个文件取决于该shell是登陆shell还是非登陆shell的 ...