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

2022-11-13 19:11:32 多线程 扩展性 详细

一、可扩展性和多线程

基于 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() 更好取决于具体情况。

到此这篇关于C++可扩展性与多线程超详细精讲的文章就介绍到这了,更多相关C++可扩展性与多线程内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

相关文章