【问题标题】:Implement Asynchronous Lazy Generator in C++用 C++ 实现异步惰性生成器
【发布时间】:2020-08-07 13:19:16
【问题描述】:

我的意图是使用通用接口来迭代来自各种 I/O 源的文件。例如,我可能想要一个迭代器,在授权允许的情况下,它会延迟打开文件系统上的每个文件并返回打开的文件句柄。然后,我想使用相同的接口来迭代可能来自 AWS S3 存储桶的对象。在后一种情况下,迭代器会将每个对象/文件从 S3 下载到本地文件系统,然后打开该文件,并再次返回文件句柄。显然,两个迭代器接口背后的实现会非常不同。

我认为三个最重要的设计目标是:

  • 对于每个iter++ 调用,将返回一个std::future 或PPL pplx::task 来表示请求的文件句柄。我需要能够执行 PPL choice(when_any) 的等效操作,因为我希望有多个迭代器同时运行。
  • 自定义迭代器实现必须是持久的/可恢复的。也就是说,它会定期记录它在文件系统扫描(或 S3 存储桶扫描等)中的位置,以便在应用程序崩溃并重新启动时尝试从最后一个已知位置恢复扫描。
  • 尽最大努力不超越 C++11(可能还有 C++14)。

我假设将 STL input_iterator 作为接口的出发点。毕竟,我看到了这个2014 SO post with a simple example。它不涉及IO,但我看到另一个article from 2001 that allegedly does incorporate IO into a custom STL iterator。到目前为止一切顺利。

当我阅读“Generator functions in C++”之类的文章时,我开始担心。确认!那篇文章给我的印象是,我无法实现创建生成器函数的意图,伪装成迭代器,可能不等待 C++20。同样,另一个2016 SO post 听起来像是用 C++ 创建生成器函数的马蜂窝。

虽然我的自定义迭代器的实现会很复杂,但也许最后两个链接要解决的问题超出了我想要实现的目标。换句话说,也许我的计划没有缺陷?如果我假设在自定义 input_iterator 后面实现惰性生成器,我想知道我正在克服哪些障碍。如果我应该使用其他东西,比如 Boost iterator_facade,我会很感激关于“为什么”的一些解释。另外,我想知道我正在做的事情是否已经在其他地方实施。也许我刚刚开始学习的 PPL 已经有了解决方案?

附言我举了一个 S3 迭代器的例子,它懒惰地下载每个请求的文件,然后返回一个打开的文件句柄。是的,我知道这意味着迭代器正在产生副作用,通常我希望避免这种情况。但是,出于我的预期目的,我不确定是否有更干净的方法来做到这一点。

【问题讨论】:

    标签: c++ boost stl generator lazy-evaluation


    【解决方案1】:

    你看过 CoroutineTS 吗?它与 C++20 一起提供,可以满足您的需求。

    一些编译器(GNU 10、MSVC)已经有了一些支持。

    您可能感兴趣的标准协程之上的特定库功能:

    • generator<T>

      cppcoro::generator<const std::uint64_t> fibonacci()
      {
        std::uint64_t a = 0, b = 1;
        while (true)
        {
          co_yield b;
          auto tmp = a;
          a = b;
          b += tmp;
        }
      }
      
      void usage()
      {
        for (auto i : fibonacci())
        {
          if (i > 1'000'000) break;
          std::cout << i << std::endl;
        }
      }
      

      一个生成器代表一个协程类型,它产生一系列类型为 T 的值,其中的值是延迟和同步产生的。

      协程主体能够使用 co_yield 关键字产生类型 T 的值。但是请注意,协程主体不能使用 co_await 关键字;值必须同步生成。

    • async_generator&lt;T&gt;

      async_generator 表示一种协程类型,它生成类型为 T 的值序列,其中值是延迟生成的,而值可能是异步生成的。

      协程主体可以同时使用 co_await 和 co_yield 表达式。

      生成器的消费者可以使用 for co_await 基于范围的 for 循环来使用这些值。

      例子

      cppcoro::async_generator<int> ticker(int count, threadpool& tp)
      {
        for (int i = 0; i < count; ++i)
        {
          co_await tp.delay(std::chrono::seconds(1));
          co_yield i;
        }
      }
      
      cppcoro::task<> consumer(threadpool& tp)
      {
        auto sequence = ticker(10, tp);
        for co_await(std::uint32_t i : sequence)
        {
          std::cout << "Tick " << i << std::endl;
        }
      }
      

    旁注:Boost Asio 在多个版本中对 CoroutineTS 提供了实验性支持,因此如果您愿意,可以将其组合起来。

    【讨论】:

    • 这是很酷的东西,但它忽略了我的目标:“尽最大努力不超越 C++11”。我正在阅读this article 并重新阅读"Generator Functions in C++" 文章,我认为我的结论是,当贡献者说我们“需要”语言支持时,他们的意思是需要语言支持才能具有语义对等和优雅像 C# 这样的语言。我不需要优雅。所以我仍然希望了解最简洁的 C++11 方法。
    猜你喜欢
    • 1970-01-01
    • 2021-11-28
    • 1970-01-01
    • 2011-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-17
    • 1970-01-01
    相关资源
    最近更新 更多