【问题标题】:PHP: How to Cleanup after Generator FunctionPHP:如何在生成器函数后进行清理
【发布时间】:2014-06-28 02:32:06
【问题描述】:

经过一些研究,我对生成器(更一般地说,迭代器)的想法很满意,因为这些任务通常会将结果缓冲到数组中,因为内存使用量是 O(1) 而不是 O(n) .

所以我打算使用生成器来处理通过 mysqli 查询的数据库结果。关于这种方法,我有 2 个问题无法找到答案,希望社区能给我一些创造性的解决方案:

  1. 如果消费代码选择不完全迭代结果,有没有办法释放生成器打开的资源?使用 Iterator 类,可以在 __desctruct 方法中执行此操作。但是,从我的测试来看,如果生成器不能自然结束,它将根本不会执行迭代序列之后的代码。我正在寻找解决方法,以防止必须创建 Iterator 子类。请参阅下面的代码。

  2. 使用生成器或迭代器是否对数据库结果有任何好处?我的一些窥探似乎表明 mysqli 可能无论如何都会将结果集加载到内存中(MYSQLI_STORE_RESULT),从而破坏了迭代器的目的。如果结果没有缓冲,我很好奇是否可以在同时迭代(获取)结果集的同时执行多个查询(想想嵌套循环,您正在迭代一组项目,然后查询每个项目的子项目父母)。这似乎数据库游标可能会在整个迭代过程中锁定。

示例

下面是我所说的清理的简化。根据我的测试,只有迭代整个结果时才会释放结果。如果消费循环中出现异常或中断,则结果永远不会被释放。也许是我想太多了,垃圾收集器足够好?

function query($mysqli, $sql){
  $result = $mysqli->query($sql);
  foreach($result as $row){
    yield $row;
  }
  $result->free(); //Never reached if break, exception, take first n rows, etc.
}

tl;dr 我只是好奇如何释放生成器使用的资源,以及随后用于数据库访问的生成器是否真的节省了任何东西,或者结果是否被缓冲了

更新

这里 (http://www.php.net/manual/en/mysqlinfo.concepts.buffering.php) 看起来像默认情况下 PHP 缓冲查询,可能违背了生成器的目的。虽然可以说只缓冲一个数组比创建一个缓冲数组的副本然后拥有两个缓冲集更好。

我正在寻找对此事有经验的人来权衡。感谢您的想法!

【问题讨论】:

    标签: php mysql mysqli


    【解决方案1】:

    我参加聚会可能有点晚了,但是如果您正在使用生成器并且需要在完成时进行清理(比如在完成循环所有内容之前中断父循环),您可以使用 try/catch /finally 在 finally 块中进行清理:

    function query($mysqli, $sql) {
      $result = $mysqli->query($sql);
      try {
        if ($result) {
          foreach($result as $row) {
            yield $row;
          }
        }
      } catch (Exception $e) {
        throw $e; // send this up the stack (or you could handle here)
      } finally {
        $result->free(); // clean up when the loop is finished.
      }
    }
    

    【讨论】:

    • 添加 finally 块使 catch 可选。如果您不处理异常,则不应捕获它。 Example
    【解决方案2】:

    以下是检测循环中断的方法,以及中断后如何处理或清理。

    function generator()
    {
        $complete = false;
        try {
    
            while (($result = some_function())) {
                yield $result;
            }
            $complete = true;
    
        } finally {
            if (!$complete) {
                // cleanup when loop breaks 
            } else {
                // cleanup when loop completes
            }
        }
    
        // Do something only after loop completes
    }
    

    【讨论】:

      【解决方案3】:
      function query($mysqli, $sql){
        $result = $mysqli->query($sql);
        foreach($result as $i => $row) {
          if ($i + 1 === $result->num_rows) {
            $result->free();
          }
          yield $row;
        }
      }
      

      【讨论】:

      • 不幸的是,这对我上面的初始解决方案没有任何好处,因为在两种情况下都必须完全迭代结果集。
      猜你喜欢
      • 2018-02-22
      • 1970-01-01
      • 1970-01-01
      • 2018-02-04
      • 1970-01-01
      • 2018-04-17
      • 1970-01-01
      • 2011-08-25
      • 2017-08-03
      相关资源
      最近更新 更多