Boost库之asio io_service以及run、run_one、poll、poll_one区别

一、io_service的作用

io_servie 实现了一个任务队列,这里的任务就是void(void)的函数。Io_servie最常用的两个接口是postrunpost向任务队列中投递任务,run是执行队列中的任务,直到全部执行完毕,并且run可以被N个线程调用。Io_service是完全线程安全的队列。

 

二、Io_servie的接口

提供的接口有runrun_onepollpoll_onestopresetdispatchpost,最常用的是runpoststop

三、Io_servie 实现代码的基本类结构:

Io_servie是接口类,为实现跨平台,采用了策略模式,所有接口均有impl_type实现。根据平台不同impl_type分为

  win_iocp_io_service Win版本的实现,这里主要分析Linux版本。

  task_io_service win平台下的实现,其代码结构为:

  detail/task_io_service_fwd.hpp 简单声明task_io_service名称

  detail/task_io_service.hpp 声明task_io_service的方法和属性

  detail/impl/task_io_service.ipp 具体实现文件

  队列中的任务类型为opertioan,原型其实是typedef task_io_service_operation operation,其实现文件在detail/task_io_service_operation.hpp中,当队列中的任务被执行时,就是task_io_service_operation:: complete被调用的时候。

四、Dispatchpost的区别

Post一定是PostQueuedCompletionStatus并且在GetQueuedCompletionStatus 之后执行。

Dispatch会首先检查当前thread是不是io_service.run/runonce/poll/poll_once线程,如果是,则直接运行。

五、Io_servie::run方法的实现

         Run方法执行队列中的所有任务,直到任务执行完毕。

  run方法首先构造一个idle_thread_info,和first_idle_thread_类型相同,即通过first_idle_thread_将所有线程串联起来,它这个串联不是立即串联的,当该线程无任务可做是加入到first_idle_thread_的首部,有任务执行时,从first_idle_thread_中断开。这很正常,因为first_idle_thread_维护的是当前空闲线程。

  加锁,循环执行do_one方法,直到do_one返回false

  do_one每次执行一个任务。首先检查队列是否为空,若空将此线程追加到first_idle_thread_的首部,然后阻塞在条件变量上,直到被唤醒。

  当被唤醒或是首次执行,若stopped_true(即此时stop方法被调用了),返回0

  队列非空,pop出一个任务,检查队列无任务那么简单的解锁,若仍有,调用wake_one_thread_and_unlock尝试唤醒其他空闲线程执行。然后执行该任务,返回1.

  实际上在执行队列任务时有一个特别的判断if (o ==&task_operation_),那么将会执行task_->runtask_变量类型为reactor,在linux平台实现为epoll_reactor,实现代码文件为detail/impl/epoll_reactor.ipprun方法实际上执行的是epoll_waitrun阻塞在epoll_wait上等待事件到来,并且处理完事件后将需要回调的函数pushio_servie的任务队列中,虽然epoll_wait是阻塞的,但是它提供了interrupt函数,该interrupt是如何实现的呢,它向epoll_wait添加一个文件描述符,该文件描述符中有8个字节可读,这个文件描述符是专用于中断epoll_wait的,他被封装到select_interrupter中,select_interrupter实际上实现是eventfd_select_interrupter,在构造的时候通过pipe系统调用创建两个文件描述符,然后预先通过write_fd8个字节,这8个字节一直保留。在添加到epoll_wait中采用EPOLLET水平触发,这样,只要select_interrupter的读文件描述符添加到epoll_wait中,立即中断epoll_wait

  Run方法的原则是:

  有任务立即执行任务,尽量使所有的线程一起执行任务

  若没有任务,阻塞在epoll_wait上等待io事件

  若有新任务到来,并且没有空闲线程,那么先中断epoll_wait,先执行任务

  若队列中有任务,并且也需要epoll_wait监听事件,那么非阻塞调用epoll_waittimeout字段设置为0),待任务执行完毕在阻塞在epoll_wait上。

  几乎对线程的使用上达到了极致。

  从这个函数中可以知道,在使用ASIO时,io_servie应该尽量多,这样可以使其epoll_wait占用的时间片最多,这样可以最大限度的响应IO事件,降低响应时延。但是每个io_servie::run占用一个线程,所以io_servie最佳应该和CPU的核数相同。

六、Io_servie::stop的实现

  加锁,调用stop_all_threads

  设置stopped_变量为true,遍历所有的空闲线程,依次唤醒

  task_interrupted_设置为true,调用task_interrupt方法。

七、resetstop

文档中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

八、run,run_one,poll,poll_one的区别

run其实就是一直循环执行do_one,并且是以阻塞方式进行的(参数为true),而run_one同样是以阻塞方式进行的,但只执行一次do_onepollrun几乎完全相同,只是它是以非阻塞方式执行do_one(参数为false)poll_one是以非阻塞方式执行一次do_one

run,run_one,poll,poll_one的实现代码,如下:

// Run the event loop until stopped or no more work.
size_t run(boost::system::error_code& ec)
{
  if (::InterlockedExchangeAdd(&outstanding_work_, 0) == 0)
  {
    stop();
    ec = boost::system::error_code();
    return 0;
  }

  call_stack<win_iocp_io_service>::context ctx(this);

  size_t n = 0;
  while (do_one(true, ec))
    if (n != (std::numeric_limits<size_t>::max)())
      ++n;
  return n;
}

// Run until stopped or one operation is performed.
size_t run_one(boost::system::error_code& ec)
{
  if (::InterlockedExchangeAdd(&outstanding_work_, 0) == 0)
  {
    stop();
    ec = boost::system::error_code();
    return 0;
  }

  call_stack<win_iocp_io_service>::context ctx(this);

  return do_one(true, ec);
}

// Poll for operations without blocking.
size_t poll(boost::system::error_code& ec)
{
  if (::InterlockedExchangeAdd(&outstanding_work_, 0) == 0)
  {
    stop();
    ec = boost::system::error_code();
    return 0;
  }

  call_stack<win_iocp_io_service>::context ctx(this);

  size_t n = 0;
  while (do_one(false, ec))
    if (n != (std::numeric_limits<size_t>::max)())
      ++n;
  return n;
}

// Poll for one operation without blocking.
size_t poll_one(boost::system::error_code& ec)
{
  if (::InterlockedExchangeAdd(&outstanding_work_, 0) == 0)
  {
    stop();
    ec = boost::system::error_code();
    return 0;
  }

  call_stack<win_iocp_io_service>::context ctx(this);

  return do_one(false, ec);
}
do_one的函数原型
size_t do_one(bool block, boost::system::error_code& ec)
{
…
BOOL ok = ::GetQueuedCompletionStatus(iocp_.handle, &bytes_transferred, &completion_key, &overlapped, block ? timeout : 0);
…
}

<think>我们讨论的是Boost.Asio中的`net::io_context`(在早期版本中称为`io_service`),它是Asio的核心,用于处理I/O事件和异步操作。`net`命名空间通常指的是`boost::asio`或C++20引入的标准网络部分(但截至C++23,标准网络仍未完全标准化,因此通常指Boost.Asio)。 ### 1. 什么是`net::io_context`? `io_context`是Asio中的I/O执行上下文,它提供了I/O事件循环、任务调度和异步操作管理。主要功能包括: - 管理I/O操作(如套接字读写) - 调度用户定义的任务(通过`post()`或`dispatch()`) - 运行事件处理循环(`run()`) ### 2. 核心机制 `io_context`使用反应器(Reactor)模式处理异步I/O事件,并支持多线程运行。其内部有一个任务队列和一个I/O事件队列(如epoll/kqueue)。 ### 3. 基本用法 #### 3.1 创建和运行 ```cpp #include <boost/asio.hpp> namespace net = boost::asio; int main() { net::io_context io_context; // 创建I/O上下文 // 运行事件循环(没有任务时会立即返回) io_context.run(); } ``` #### 3.2 提交任务 ```cpp // 提交一个任务到io_context的任务队列 io_context.post([]{ std::cout << "Hello from io_context!\n"; }); // 启动事件循环(会执行该任务) io_context.run(); ``` #### 3.3 结合异步I/O ```cpp net::ip::tcp::socket socket(io_context); // 异步连接 socket.async_connect(endpoint, [](const boost::system::error_code& ec) { if (!ec) { // 连接成功 } }); // 运行事件循环处理连接操作 io_context.run(); ``` ### 4. 多线程运行 多个线程可以同时调用`run()`方法,形成线程池: ```cpp net::io_context io_context; std::vector<std::thread> threads; const int num_threads = 4; // 提交初始任务(可选) io_context.post([]{ /* ... */ }); // 创建线程池 for (int i = 0; i < num_threads; ++i) { threads.emplace_back([&io_context]() { io_context.run(); }); } // 等待所有线程结束 for (auto& t : threads) { t.join(); } ``` ### 5. 停止`io_context` 通过`stop()`方法可以停止事件循环,但已入队的任务可能不会被执行: ```cpp io_context.stop(); // 所有线程的run()将退出 ``` ### 6. 常见问题 #### 6.1 `run()`返回过早 当没有更多的工作(如异步操作或任务)时,`run()`会返回。解决方案: - 使用`work_guard`保持运行: ```cpp #include <boost/asio/executor_work_guard.hpp> using work_guard = net::executor_work_guard<net::io_context::executor_type>; work_guard wg(io_context.get_executor()); // 阻止run()返回 io_context.run(); wg.reset(); // 允许run()返回 ``` #### 6.2 线程安全 - `io_context`的方法(如`post()`)是线程安全的。 - 但异步操作完成处理程序(回调)需要用户自己保证线程安全(例如使用锁)。 ### 7. 性能优化 - 使用`io_context`的`poll()`或`poll_one()`进行非阻塞检查。 - 在多核系统上,可以为每个CPU核心创建一个`io_context`实例(即多个I/O上下文)。 ### 示例:完整的异步TCP服务器框架 ```cpp #include <boost/asio.hpp> #include <iostream> namespace net = boost::asio; using tcp = net::ip::tcp; class Session : public std::enable_shared_from_this<Session> { public: Session(tcp::socket socket) : socket_(std::move(socket)) {} void start() { read(); } private: void read() { auto self(shared_from_this()); socket_.async_read_some(net::buffer(data_), [this, self](boost::system::error_code ec, std::size_t length) { if (!ec) { write(length); } }); } void write(std::size_t length) { auto self(shared_from_this()); net::async_write(socket_, net::buffer(data_, length), [this, self](boost::system::error_code ec, std::size_t /*length*/) { if (!ec) { read(); } }); } tcp::socket socket_; char data_[1024]; }; class Server { public: Server(net::io_context& io_context, short port) : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) { do_accept(); } private: void do_accept() { acceptor_.async_accept( [this](boost::system::error_code ec, tcp::socket socket) { if (!ec) { std::make_shared<Session>(std::move(socket))->start(); } do_accept(); // 继续接受下一个连接 }); } tcp::acceptor acceptor_; }; int main() { net::io_context io_context; Server server(io_context, 8080); io_context.run(); // 运行事件循环 return 0; } ``` ### 8. 与标准的关联 C++20开始,`std::net`被提议,但尚未完全标准化。目前建议使用Boost.Asio,其接口可能成为标准。 §§相关问题§§: 1. `io_context::run()`和`io_context::poll()`有什么区别? 2. 如何安全地停止一个正在运行的`io_context`? 3. 在多线程环境中使用`io_context`需要注意什么? 4. `executor_work_guard`的作用是什么?</think>### `net::io_context` 详解 `net::io_context` 是 Boost.Asio 和 C++ 标准网络部分的核心组件(在 C++20 中引入实验性支持),它提供异步 I/O 操作的事件循环机制。以下是其关键特性和用法: #### 1. **核心功能** - **事件循环**:处理 I/O 完成事件、定时器和用户提交的任务 - **异步操作**:支持套接字 I/O、定时器、信号处理等异步操作 - **任务调度**:通过 `post()`/`dispatch()` 提交自定义任务 #### 2. **基本用法** ```cpp #include <boost/asio.hpp> namespace net = boost::asio; // 或 std::experimental::net (C++20) int main() { net::io_context io_ctx; // 创建 I/O 上下文 // 提交任务到 I/O 上下文 io_ctx.post([] { std::cout << "Task 1 executed\n"; }); // 运行事件循环(处理一个任务) std::cout << "Tasks completed: " << io_ctx.poll() << "\n"; // 提交另一个任务 io_ctx.post([] { std::cout << "Task 2 executed\n"; }); // 运行所有任务直到完成 io_ctx.run(); } ``` #### 3. **异步 I/O 操作示例(TCP 服务器)** ```cpp #include <boost/asio.hpp> namespace net = boost::asio; using tcp = net::ip::tcp; void handle_accept(tcp::socket&& socket) { auto data = std::make_shared<std::array<char, 1024>>(); socket.async_read_some(net::buffer(*data), [socket = std::move(socket), data](boost::system::error_code ec, size_t len) mutable { if (!ec) { std::cout << "Received: " << std::string_view(data->data(), len) << "\n"; } }); } int main() { net::io_context io_ctx; tcp::acceptor acceptor(io_ctx, {tcp::v4(), 8080}); // 异步接受连接 std::function<void()> do_accept; do_accept = [&] { acceptor.async_accept([&](boost::system::error_code ec, tcp::socket socket) { if (!ec) handle_accept(std::move(socket)); do_accept(); // 继续接受新连接 }); }; do_accept(); io_ctx.run(); // 启动事件循环 } ``` #### 4. **关键方法** | 方法 | 说明 | |------|------| | `run()` | 阻塞运行事件循环直到无任务 | | `run_one()` | 执行一个就绪任务 | | `poll()` | 非阻塞执行所有就绪任务 | | `poll_one()` | 非阻塞执行一个就绪任务 | | `stop()` | 停止事件循环 | | `restart()` | 重置停止状态 | | `post(f)` | 异步提交任务(入队) | | `dispatch(f)` | 尝试立即执行任务(可能同步) | #### 5. **多线程处理模式** ```cpp net::io_context io_ctx; net::executor_work_guard<decltype(io_ctx.get_executor())> work = net::make_work_guard(io_ctx); // 防止 run() 提前退出 // 创建工作线程池 std::vector<std::thread> threads; for (int i = 0; i < 4; ++i) { threads.emplace_back([&io_ctx] { std::cout << "Thread " << std::this_thread::get_id() << " started\n"; io_ctx.run(); std::cout << "Thread " << std::this_thread::get_id() << " exited\n"; }); } // 提交任务(会被任意工作线程执行) io_ctx.post([]{ std::cout << "Task executed by " << std::this_thread::get_id() << "\n"; }); // 停止处理 work.reset(); // 允许 run() 退出 for (auto& t : threads) t.join(); ``` #### 6. **常见问题与解决方案** 1. **事件循环提前退出** - **原因**:没有待处理的操作 - **解决**:使用 `executor_work_guard` ```cpp auto work = net::make_work_guard(io_ctx); io_ctx.run(); // 会持续运行直到 work.reset() ``` 2. **线程安全问题** - **规则**:除 `post()/dispatch()` 外,其他方法需在运行线程调用 - **安全操作**: ```cpp io_ctx.post([&] { io_ctx.stop(); // 安全:在 I/O 线程中执行 }); ``` 3. **性能优化** - 每个 CPU 核心使用一个 `io_context` - 限制任务切换:使用 `strand` 保证顺序执行 ```cpp net::strand<net::io_context::executor_type> my_strand = net::make_strand(io_ctx); net::post(my_strand, []{ /* 顺序执行的任务 */ }); ``` #### 7. **与标准的兼容性** - **C++20**:`<experimental/net>` 提供类似接口 - **C++23**:计划标准化为 `std::io_context` - **移植提示**: ```cpp #ifdef USE_STD_NET namespace net = std::experimental::net; #else namespace net = boost::asio; #endif ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

byxdaz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值