asio C++ library核心理念和功能(二)

1.2.3 线程和Asio

线程安全
一般来说,并发使用不同的对象是安全的,使用单个对象是不安全的。 但是,像io_service这样的类型可以更有力地保证可以同时使用单个对象。

线程池
多线程可能会调用io_service :: run()来设置线程池,从中可以调用完成处理程序。 这种方法也可以与io_service :: post()一起使用,以便在线程池中执行任何计算任务。
请注意,所有已加入io_service池的线程都被认为是等同的,并且io_service可以以任意方式在他们之间分配工作。

内部线程
针对特定平台的这个库的实现可以利用一个或多个内部线程来模拟异步性。尽可能地,这些线程必须对库用户不可见。 特别是,线程:

  • 不能直接调用用户的代码;
  • 必须阻止所有信号
    这种做法有以下保证作为补充:
  • 异步完成处理程序只能从当前调用io_service :: run()的线程中调用。
    因此,库用户有责任创建和管理通知将传送到的所有线程。
    这种方法的原因包括:
  • 通过仅从一个线程调用io_service :: run(),用户的代码可以避免与同步相关的开发复杂性。 例如,库用户可以实现单线程的可伸缩服务器(从用户的角度来看)。
  • 库用户可能需要在线程启动后,执行任何其他应用程序代码之前不久,在线程中执行初始化。 例如,Microsoft的COM用户必须先调用CoInitializeEx,然后才能从该线程调用任何其他COM操作。
  • 库接口与用于线程创建和管理的接口分离,并允许在线程不可用的平台上实现。

1.2.4 链:使用线程而不显式锁定

链被定义为事件处理程序的严格顺序调用(即无并发调用)。 链的使用允许执行代码在多线程程序中,而不需要显式锁定(例如使用互斥锁)。
链可以是隐含的也可以是明确的,如以下替代方法所示:

  • 仅从一个线程调用io_service :: run()意味着所有事件处理程序都在隐式链中执行,这是由于io_service保证处理程序只能从run()内部调用
  • 在存在与连接相关联的单个异步操作链的情况下(例如,在诸如HTTP的半双工协议实现中),不存在并发执行处理程序的可能性。 这是一条隐含的链条。
  • 显式链是io_service :: strand的一个实例。 所有事件处理函数对象都需要使用io_service :: strand :: wrap()进行包装,否则通过io_service :: strand对象进行发布/分派。
    在组成异步操作的情况下,如async_read()或async_read_until(),如果完成处理程序经过一个链,则所有中间处理程序也应该经过同一链。 这需要确保线程安全地访问调用者和组合操作之间共享的对象(在async_read()的情况下,它是套接字,调用者可以关闭()以取消操作)。 这是通过为所有中间处理程序提供钩子函数来完成的,该函数将调用转发给与最终处理程序关联的可定制钩子:
struct my_handler
{
void operator()() { ... }
};
template<class F>
void asio_handler_invoke(F f, my_handler*)
{
// Do custom invocation here.
// Default implementation calls f();
}

io_service :: strand :: wrap()函数创建了一个新的完成处理程序,它定义了asio_handler_invoke,以便函数对象通过该线程执行。

1.2.5 缓冲区

从根本上讲,I / O涉及将数据传入和传出连续的内存区域,称为缓冲区。 这些缓冲区可以简单地表示为由指针和字节大小组成的元组。 但是,为了开发高效的网络应用程序,Asio支持分散 - 集中操作。 这些操作涉及一个或多个缓冲区:

  • 从多个的缓冲区中分散读
  • 向多个缓冲区集中写入
    因此,我们需要一个抽象来表示一组缓冲区。 Asio使用的方法是定义一个类型(实际上是两种类型)来表示单个缓冲区。 这些可以存储在一个容器中,可以传递给分散 - 集中操作。
    除了将缓冲区指定为指针和字节大小之外,Asio还在可修改内存(称为可变内存)和不可修改内存(其中后者是从用于const限定变量的存储创建的)之间进行了区分。 因此这两种类型可以定义如下:
typedef std::pair<void*, std::size_t> mutable_buffer;
typedef std::pair<const void*, std::size_t> const_buffer;

这里,一个mutable_buffer可以转换为一个const_buffer,但是逆向的转换是无效的。
但是,Asio并没有按原样使用上述定义,而是定义了两个类:mutable_buffer和const_buffer。
这些目标是提供对连续内存的不透明表示,其中:

  • 类型在转换中表现为std :: pair。 也就是说,mutable_buffer可以转换为const_buffer,但是相反的转换是不允许的。
  • 有防止缓冲区溢出的保护。 给定一个缓冲区实例,用户只能创建另一个代表相同范围的内存或其子范围的缓冲区。 为了进一步提高安全性,库还包括自动机制,从数组中确定缓冲区的大小,POD元素的boost :: array或std :: vector或std :: string
  • 必须使用buffer_cast函数明确请求类型安全违规。 通常,应用程序永远不需要这样做,但是库实现需要将原始内存传递到底层操作系统函数。
    最后,通过将缓冲区对象放入容器中,可以将多个缓冲区传递给分散 - 收集操作(例如read()或write())。 MutableBufferSequence和ConstBufferSequence概念已被定义,因此可以使用诸如std :: vector,std :: list,std :: vector或boost :: array之类的容器。

流缓冲用于集成IOSTREAMS
类asio :: basic_streambuf源自std :: basic_streambuf,用于将输入序列和输出序列与某个字符数组类型的一个或多个对象相关联,这些对象的元素存储任意值。 这些字符数组对象是streambuf对象的内部对象,但提供对数组元素的直接访问以允许它们与I / O操作一起使用,例如套接字的发送或接收操作:

  • streambuf的输入序列可通过data()成员函数访问。 这个函数的返回类型符合ConstBufferSequence的要求。
  • streambuf的输出顺序可以通过prepare()成员函数来访问。 该函数的返回类型符合
    MutableBufferSequence要求。
  • 通过调用commit()成员函数将数据从输出序列的前面传输到输入序列的后面
  • 通过调用consume()成员函数将数据从输入序列的前面移除。
    streambuf构造函数接受一个size_t参数,用于指定输入序列和输出序列大小总和的最大值。 如果成功,任何将超出此限制的内部数据增加的操作都会抛出一个std :: length_error异常。

缓冲区序列的顺序遍历
buffers_iterator <>类模板允许缓冲区序列(即满足MutableBufferSequence或ConstBufferSequence要求的类型)遍历,就像它们是连续的字节序列一样。 还提供了名为buffers_begin()和buffers_end()的辅助函数,其中会自动推导出buffers_iterator <>模板参数。
作为一个例子,为了从一个套接字读入一个单行并写入一个std :: string,你可以这样写:

asio::streambuf sb;
...
std::size_t n = asio::read_until(sock, sb, ’\n’);
asio::streambuf::const_buffers_type bufs = sb.data();
std::string line(
asio::buffers_begin(bufs),
asio::buffers_begin(bufs) + n);

缓冲区调试
某些标准库实现(如Microsoft Visual C ++ 8.0及更高版本附带的标准库实现)提供了称为迭代器调试的功能。 这意味着在运行时检查迭代器的有效性。 如果程序试图使用已经失效的迭代器,则会触发断言。 例如:

std::vector<int> v(1)
std::vector<int>::iterator i = v.begin();
v.clear(); // invalidates iterators
*i = 0; // assertion!

Asio利用此功能来添加缓冲区调试。 考虑下面的代码:

void dont_do_this()
{
std::string msg = "Hello, world!";
asio::async_write(sock, asio::buffer(msg), my_handler);
}

在调用异步读或写时,需要确保操作的缓冲区有效,直到调用完成处理程序。在上面的例子中,缓冲区是std :: string变量msg。这个变量在堆栈中,所以就这样了
在异步操作完成之前超出范围。如果你很幸运,那么应用程序会崩溃,但随机失败更可能。
启用缓冲区调试时,Asio会将迭代器存储到字符串中,直到异步操作完成,然后将其解引用以检查其有效性。在上面的例子中,你会在Asio尝试调用完成处理程序之前观察断言失败。
当定义_GLIBCXX_DEBUG时,此功能会自动提供给Microsoft Visual Studio 8.0或更高版本以及GCC。这种检查有一个性能成本,所以缓冲区调试只能在调试版本中启用。对于其他编译器,可以通过定义ASIO_ENABLE_BUFFER_DEBUGGING来启用。它也可以通过定义ASIO_DISABLE_BUFFER_DEBUGGING来显式禁用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容