亲宝软件园·资讯

展开

C++可扩展性与多线程超详细精讲

无水先生 人气:0

一、可扩展性和多线程

基于 Boost.Asio 之类的库开发程序与通常的 C++ 风格不同。可能需要更长时间才能返回的函数不再按顺序调用。 Boost.Asio 不调用阻塞函数,而是启动异步操作。操作完成后应该调用的函数现在在相应的处理程序中调用。这种方法的缺点是顺序执行函数的物理分离,这会使代码更难理解。

诸如 Boost.Asio 之类的库通常用于实现更高的效率。无需等待操作完成,程序可以在其间执行其他任务。因此,可以启动多个同时执行的异步操作——请记住,异步操作通常用于访问进程之外的资源。由于这些资源可以是不同的设备,它们可以独立工作并同时执行操作。

可扩展性描述了程序有效地从额外资源中受益的能力。借助 Boost.Asio,可以从外部设备同时执行操作的能力中受益。如果使用线程,则可以在可用的 CPU 内核上同时执行多个功能。 Boost.Asio 的线程提高了可伸缩性,因为您的程序可以利用内部和外部设备,这些设备可以独立执行操作或相互协作执行操作。

如果在 boost::asio::io_service 类型的对象上调用成员函数 run(),则在同一线程中调用关联的处理程序。通过使用多个线程,程序可以多次调用 run()。一旦异步操作完成,I/O 服务对象将在这些线程之一中执行处理程序。如果第二个操作在第一个操作之后不久完成,则 I/O 服务对象可以在不同的线程中执行处理程序。现在,不仅进程外的操作可以并发执行,进程内的处理程序也可以并发执行。

二、线程示例

示例 32.3。 I/O 服务对象的两个线程同时执行处理程序

#include <boost/asio/io_service.hpp>
#include <boost/asio/steady_timer.hpp>
#include <chrono>
#include <thread>
#include <iostream>
using namespace boost::asio;
int main()
{
  io_service ioservice;
  steady_timer timer1{ioservice, std::chrono::seconds{3}};
  timer1.async_wait([](const boost::system::error_code &ec)
    { std::cout << "3 sec\n"; });
  steady_timer timer2{ioservice, std::chrono::seconds{3}};
  timer2.async_wait([](const boost::system::error_code &ec)
    { std::cout << "3 sec\n"; });
  std::thread thread1{[&ioservice](){ ioservice.run(); }};
  std::thread thread2{[&ioservice](){ ioservice.run(); }};
  thread1.join();
  thread2.join();
}

前面的示例已在示例 32.3 中转换为多线程程序。使用 std::thread,在 main() 中创建了两个线程。在每个线程中的唯一 I/O 服务对象上调用 run()。这使得 I/O 服务对象可以在异步操作完成时使用两个线程来执行处理程序。

在示例 32.3 中,两个闹钟都应在三秒后响起。因为有两个线程可用,所以两个 lambda 函数可以同时执行。如果在执行第一个闹钟的处理程序时第二个闹钟响起,则可以在第二个线程中执行该处理程序。如果第一个闹钟的handler已经返回,I/O服务对象可以使用任意线程执行第二个handler。

当然,使用线程并不总是有意义的。示例 32.3 可能不会将消息按顺序写入标准输出流。相反,它们可能会混淆。两个处理程序可能同时在两个线程中运行,共享全局资源 std::cout。为避免中断,需要同步对 std::cout 的访问。如果处理程序不能同时执行,线程的优势就丧失了。

示例 32.4。两个 I/O 服务对象各有一个线程并发执行处理程序

#include <boost/asio/io_service.hpp>
#include <boost/asio/steady_timer.hpp>
#include <chrono>
#include <thread>
#include <iostream>
using namespace boost::asio;
int main()
{
  io_service ioservice1;
  io_service ioservice2;
  steady_timer timer1{ioservice1, std::chrono::seconds{3}};
  timer1.async_wait([](const boost::system::error_code &ec)
    { std::cout << "3 sec\n"; });
  steady_timer timer2{ioservice2, std::chrono::seconds{3}};
  timer2.async_wait([](const boost::system::error_code &ec)
    { std::cout << "3 sec\n"; });
  std::thread thread1{[&ioservice1](){ ioservice1.run(); }};
  std::thread thread2{[&ioservice2](){ ioservice2.run(); }};
  thread1.join();
  thread2.join();
}

对单个 I/O 服务对象重复调用 run() 是使基于 Boost.Asio 的程序更具可扩展性的推荐方法。但是,您也可以创建多个 I/O 服务对象,而不是为一个 I/O 服务对象提供多个线程。

在示例 32.4 中,两个 I/O 服务对象在两个类型为 boost::asio::steady_timer 的闹钟旁边使用。该程序基于两个线程,每个线程绑定到另一个 I/O 服务对象。两个 I/O 对象 timer1 和 timer2 不再绑定到同一个 I/O 服务对象。它们绑定到不同的对象。

示例 32.4 的工作方式与之前相同。无法就何时使用多个 I/O 服务对象提供一般性建议。因为 boost::asio::io_service 代表一个操作系统接口,所以任何决定都取决于特定的接口。

在 Windows 上,boost::asio::io_service 通常基于 IOCP,在 Linux 上,它基于 epoll()。拥有多个 I/O 服务对象意味着将使用多个 I/O 完成端口,或者将多次调用 epoll()。这是否比仅使用一个 I/O 完成端口或一次调用 epoll() 更好取决于具体情况。

加载全部内容

相关教程
猜你喜欢
用户评论