【问题标题】:Unsafe to delete items from DB_File tied hash while iterating with `each`?使用“each”进行迭代时从 DB_File 绑定哈希中删除项目不安全?
【发布时间】:2014-06-18 11:38:05
【问题描述】:

问题

我正在使用 NetBSD 6.1、Perl v5.18.1 和 DB_File v1.818。如果我使用 each 遍历与 DB_File 相关的哈希并从哈希中删除每个项目,则并非所有项目都被删除。这是一个演示问题的脚本:

use strict;
use warnings;
use DB_File;

my $dbfile = "/tmp/foo.db";
! -f $dbfile or unlink($dbfile) or die("unable to delete $dbfile");

my %db;
tie(%db, "DB_File", "/tmp/foo.db", O_RDWR|O_CREAT, 0644);

# add some random records
my @chars = ("0".."9", "a".."f");
for (1..10) {
    my ($key, $val);
    $key .= $chars[rand(@chars)] for 1..10;
    $val .= $chars[rand(@chars)] for 1..32;
    $db{$key} = $val;
}

# this doesn't delete everything from the database!
keys(%db); # reset the iterator
while (my ($key, $val) = each(%db)) {
    delete $db{$key};
}

foreach (keys(%db)) {
    print("\$db{$_} = $db{$_}\n");
}

untie(%db);

当我运行它时,10 条记录中有 4 条(有时是 5 条)没有被删除:

$db{4a8e5792e0} = 7a4d078a3f0f3cba750cb395fcc3343d
$db{d28e8cb226} = 17af1122f0b94113416693b1c4165954
$db{a3ae4e2e24} = 3c15270cf16601722bd8106b1727dbc2
$db{886c469eb4} = f1496f83f7866d09c9e28aae8e1b62e6
$db{2c53ebd993} = facfe8228240878aac825de4d97ca22b

如果我在 Linux (Ubuntu 14.04) 系统上运行脚本,那么它总是可以工作(所有记录都已删除)。

如果我在键上切换到foreach 循环,那么它可以在 NetBSD 和 Linux 上运行:

# this always works
foreach (keys(%db)) {
    delete $db{$_};
}

文档是怎么说的

我找不到任何明确说明通过each 迭代时删除并不总是有效的内容。

这是我能找到的:

  • documentation for foreach 说:

    如果 VAR 是一个并列变量或其他特殊变量,foreach 可能不会像您预期的那样。

    我不确定这是什么意思,但奇怪的是 foreach 的情况是它确实起作用的地方。

  • documentation for each 说:

    任何插入到散列中的操作都可能改变顺序,任何删除操作都会改变,但eachkeys 返回的最新键可以在不改变顺序的情况下被删除。

    对我来说,这意味着在迭代时delete 当前条目是安全的。

  • documentation for DB_File 在迭代时没有提到删除。

问题

是不是这个问题:

  • NetBSD's Berkeley DB implementation 中的错误引起?
  • 是由 DB_File 中的错误引起的?
  • each 的已知限制?
  • 绑定哈希的已知限制?
  • DB_File 绑定哈希的已知限制?

为什么键上的foreach 有效,而each 无效?

【问题讨论】:

  • “对我来说,这意味着在迭代时delete 当前条目是安全的。”对于哈希,是的。但是这里没有哈希。 Perl 的文档不能声明 DB_File 支持或不支持什么。
  • @ikegami:好的,这样就排除了“each 的已知限制”。对吗?

标签: perl each netbsd tie


【解决方案1】:

我的直觉是这种行为是绑定哈希的限制。似乎无法保证 DB_File 在删除最近获取的密钥时不会重新散列。

你也问了和

的区别
while (my ($key, $val) = each(%db)) {
    delete $db{$key};
}

foreach (keys(%db)) {
    delete $db{$_};
}

.

在第一种情况下,您在迭代绑定哈希时干预了绑定哈希。因此,迭代器有可能无法到达所有键。

在第二种情况下,您首先迭代绑定的哈希以获得完整的键列表。循环遍历完整列表时,您一定会删除所有条目。

【讨论】:

  • 哦,我的错误印象是keys() 发出了一个类似 Python 的“生成器”,用于在非常大的(可能绑定的)哈希上进行有效迭代。很高兴知道,谢谢!
  • 假设可以使用 NetBSD 的 Berkeley DB 实现编写一个可以在不使迭代器失效的情况下在迭代时删除的 C 程序,那么应该可以编写 DB_File 以实现类似的行为。因此,在我看来,这是多种事物的结合:(1)将行为留给实现(允许实现表现得很好或可怕)和(2)DB_File 的实现不使用数据库最佳接口或 NetBSD DB 实现使得在迭代时无法安全删除(很高兴知道哪个)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-08
  • 1970-01-01
  • 2023-04-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多