0%

[boost.asio] 3.Overview (1)

Threads and Boost.Asio#

线程安全(Thread Safety)#

并发(concurrent)在不同对象间是安全的,但对于单一对象,却是不安全的。然而 io_context等类型可以保证 单一对象的并发线程安全

线程池(Thread Pools)#

多个线程可以调用 io_context::run()来建立一个线程池,从中可以调用完成处理程序。这种方法也可以与post()一起使用,作为跨线程池执行任意计算任务的方法。

所有加入到io_context's pool的线程都是对等的,io_context可以任意方式分发工作。

内部线程(Internal Threads)#

为了实现异步,有一些调用者不可见的 内部线程,他们必须满足: - 不能被库函数使用者 直接调用 - 屏蔽所有signal

这种方法辅之以以下保证:

  • 异步completion handlers程序将仅从当前正在调用io_context::run()的线程调用。

因此,库用户需要 创建 和 管理所有收消息的线程

这种方法的原因包括:

  • 单线程调用io_context::run(),可以避免与同步相关的开发复杂性。例如,库用户可以实现单线程的可扩展服务器
  • 库用户需要在线程启动后以及执行其他应用程序代码之前在线程中执行初始化。
  • 库接口与线程创建和管理接口解耦合,并允许在线程不可用的平台上实现

Strands: 使用没有显式锁定的线程(Strands: Use Threads Without Explicit Locking)#

Strand定义为event handlers的严格顺序调用(即没有并发调用)。使用Strand允许在多线程中执行代码,而无需显式加锁(例如,使用互斥)。

Strands可implicit 或 explicit: - 由于io_context保证了event handlers仅从io_context::run()里面触发,因此在单线程中调用 io_context::run(),意味着所有的event handlers在implicit strand中执行。 - 当与连接相关联的单链异步操作(例如采用半双工协议,如HTTP),是不可能存在并发的handlers,这种情况就是implicit strand。 - explicit strand是 strand<> or io_context::strand 的实例。所有的event handler函数 需要使用 boost::asio::bind_executor()绑定到strand上 或 通过strand对象发布/调度(posted/dispatched)

对于组合异步操作,如async_read()async_read_until(),如果completion handler通过strand,则所有中间handlers也应该经历相同的strand。这是确保调用方和组合操作之间共享的任何对象的线程安全

为此,所有异步操作都使用get_associated_executor函数获取handler的关联执行器。例如:

1
boost::asio::associated_executor_t<Handler> a = boost::asio::get_associated_executor(h);
通过指定嵌套类型executor_type成员函数get_executor(),可以针对特定处理程序类型自定义执行器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class my_handler
{
public:
// Custom implementation of Executor type requirements.
typedef my_executor executor_type;

// Return a custom executor implementation.
executor_type get_executor() const noexcept
{
return my_executor();
}

void operator()() { ... }
};
在更复杂的情况下,associated_executor模板可能直接偏特化(partially specialised):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct my_handler
{
void operator()() { ... }
};

namespace boost { namespace asio {

template <class Executor>
struct associated_executor<my_handler, Executor>
{
// Custom implementation of Executor type requirements.
typedef my_executor type;

// Return a custom executor implementation.
static type get(const my_handler&,
const Executor& = Executor()) noexcept
{
return my_executor();
}
};

} } // namespace boost::asio

boost::asio::bind_executor()函数用于绑定特殊executor对象(如一个strand)到completion handler。例如:

1
2
3
4
5
6
my_socket.async_read_some(my_buffer,
boost::asio::bind_executor(my_strand,
[](error_code ec, size_t length) // 这是个lambda函数
{
// ...
}));

Buffers#

I/O 涉及数据在内存的连续区域(称为缓冲区)之间传输,这些缓冲区可以简单地表示为由指针和字节大小组成的元组。为了开发高效的网络应用程序,Boost.Asio 包括对分散收集(scatter-gather)操作的支持:

  • 散点读取(scatter-read)将数据接收到多个缓冲区中。
  • 收集写入(gather-write)传输多个缓冲区

除了将缓冲区指定为指针和大小(以字节为单位)之外,Boost.Asio 还区分了可修改内存(称为mutable)和非可修改内存(其中后者从存储中创建为 const 限定变量)。因此,这两种类型可以定义如下:

1
2
typedef std::pair<void*, std::size_t> mutable_buffer;
typedef std::pair<const void*, std::size_t> const_buffer;
mutable_buffer 可以转换为const_buffer,反之不行。

但是,Boost.Asio 没有使用上述定义,而是定义两个类:mutable_buffer和const_buffer。这个目的是提供连续内存的不透明表示形式,其中:

  • 转换行为与std::pair一样:mutable_buffer可单向转换为const_buffer,反之不行。
  • 有缓冲区溢出保护。给定一个缓冲区实例,用户只能创建另一个表示相同内存范围或其子范围的缓冲区。为了提供进一步的安全性,库还包括用于从 POD 元素的数组、boost::array或std::vector或std::string自动确定缓冲区大小的机制。
  • 使用data()成员函数显式访问底层内存。通常应用程序不需要这样做,但库实现需要将原始内存传递给底层操作系统函数

最后,通过将缓冲区对象放入容器中,可以将多个缓冲区传递给scatter-gather操作(如read()或 write())。MutableBufferSequence和ConstBufferSequence概念已经定义好,这样就可以使用诸如std::vector、std::list、std::array或boost::array之类的容器。

与Iostreams集成的Streambuf#

boost::asio::basic_streambuf 类继承自 std::basic_streambuf,可以将 输入和输出序列(sequence) 关联到一个或多个 字符串数组对象,这些元素存储任意值。这些字符数组对象是 streambuf 对象的内部对象,但提供了对数组元素的直接访问,以允许它们用于 I/O 操作,例如套接字的发送或接收操作

缓冲序列的字节遍历#

buffers_iterator<>模板类,允许遍历buffer序列(即满足MutableBufferSequence或ConstBufferSequence要求的类型),就好像它们是连续的字节序列一样。还提供了名为buffers_begin()和buffers_end()的help函数。

例如,从socket读取一行,并且转换为string:

1
2
3
4
5
6
7
boost::asio::streambuf sb;
...
std::size_t n = boost::asio::read_until(sock, sb, '\n');
boost::asio::streambuf::const_buffers_type bufs = sb.data();
std::string line(
boost::asio::buffers_begin(bufs),
boost::asio::buffers_begin(bufs) + n);

缓冲区调试#

某些标准库的实现(如Microsoft Visual C++ 8.0 和更高版本一起搭载的库)提供了一个称为迭代器调试的特性。这意味着在运行时检查迭代器的有效性。如果程序尝试使用已失效的迭代器,则会触发断言。例如:

1
2
3
4
std::vector<int> v(1)
std::vector<int>::iterator i = v.begin();
v.clear(); // invalidates iterators
*i = 0; // assertion!
Boost.Asio 利用此功能添加缓冲区调试。请考虑以下代码:
1
2
3
4
5
void dont_do_this()
{
std::string msg = "Hello, world!";
boost::asio::async_write(sock, boost::asio::buffer(msg), my_handler);
}
调用异步读或写时,需要确保操作的缓冲区调用completion handler之前是有效的。在上面的示例中,缓冲区是std::string变量msg。这个变量在栈上(局部变量),因此在异步操作完成之前它就超出了函数的范围(scope)。如果幸运的话,应用程序会crash,但随机失败的可能性更大。

启用缓冲区调试时,Boost.Asio 将迭代器存储到字符串中,直到异步操作完成,然后取消引用以检查其有效性。

此功能对 Microsoft Visual Studio 8.0 或更高版本可用,而 GCC 需要定义了**_GLIBCXX_DEBUG时才可用。此检查会带来性能开销,因此仅在调试生成中启用缓冲区调试。对于其他编译器,可以通过定义 BOOST_ASIO_ENABLE_BUFFER_DEBUGGING来启用它。也可以通过定义 BOOST_ASIO_DISABLE_BUFFER_DEBUGGING来显式禁用**。