0

我曾经File::Find遍历目录树和Win32::File'sGetAttributes函数来查看在其中找到的文件的属性。这在单线程程序中有效。

然后我将目录遍历移到一个单独的线程中,它停止工作。GetAttributes对每个文件都失败,并在$^E.

我将问题追溯到File::Find使用的事实chdir,并且显然GetAttributes不使用当前目录。我可以通过将绝对路径传递给它来解决这个问题,但是我可能会遇到路径长度限制,并且在运行这个脚本的地方肯定会出现长路径,所以我真的需要利用chdir相对路径。

为了演示这个问题,这里有一个脚本,它在当前目录中创建一个文件,在子目录中创建另一个文件,chdir's 到子目录,并以 3 种方式查找文件:system("dir")openGetAttributes.

当脚本不带参数运行时,dir显示子目录,在子目录中open找到文件,并GetAttributes成功返回其属性。当使用 运行时--thread,所有测试都在子线程中完成,dir并且open仍然可以工作,但是GetAttributes失败了。然后它调用GetAttributes原始目录中的文件(我们已经从 chdir 中删除了)并找到了那个文件!不知何故GetAttributes正在使用进程的原始工作目录 - 或者可能是主线程的工作目录 - 与所有其他文件操作不同。

我怎样才能解决这个问题?如果这很重要,我可以保证主线程不会做任何 chdir'ing。

use strict;
use warnings;

use threads;
use Data::Dumper;
use Win32::File qw/GetAttributes/;
sub doit
{
  chdir("testdir") or die "chdir: $!\n";
  system "dir";
  my $attribs;
  open F, '<', "file.txt" or die "open: $!\n";
  print "open succeeded. File contents:\n-------\n", <F>, "\n--------\n";
  close F;
  my $x = GetAttributes("file.txt", $attribs);
  print Dumper [$x, $attribs, $!, $^E];
  if(!$x) {
    # If we didn't find the file we were supposed to find, how about the
    # bad one?
    $x = GetAttributes("badfile.txt", $attribs);
    if($x) {
      print "GetAttributes found the bad file!\n";
      if(open F, '<', "badfile.txt") {
        print "opened the bad file\n";
        close F;
      } else {
        print "But open didn't open it. Error: $! ($^E)\n";
      }
    }
  }
}

# Setup
-d "testdir" or mkdir "testdir" or die "mkdir testdir: $!\n";
if(!-f "badfile.txt") {
  open F, '>', "badfile.txt" or die "create badfile.txt: $!\n";
  print F "bad\n";
  close F;
}
if(!-f "testdir/file.txt") {
  open F, '>', "testdir/file.txt" or die "create testdir/file.txt: $!\n";
  print F "hello\n";
  close F;
}

# Option 1: do it in the main thread - works fine
if(!(@ARGV && $ARGV[0] eq '--thread')) {
  doit();
}

# Option 2: do it in a secondary thread - GetAttributes fails
if(@ARGV && $ARGV[0] eq '--thread') {
  my $thr = threads->create(\&doit);
  $thr->join();
}
4

1 回答 1

0

最终,我发现 perl 正在维护某种仅适用于 perl 内置运算符的辅助 cwd,同时GetAttributes使用本机 cwd。我不知道它为什么会这样做,或者为什么它只发生在辅助线程中;我最好的猜测是 perl 正在尝试模拟每个进程一个 cwd 的 unix 规则,并且由于Win32::*模块无法正常运行而失败。

不管是什么原因,只要您要进行Win32::*操作,就可以通过强制本机 cwd 与 perl 的 cwd 相同来解决它,如下所示:

use Cwd;
use Win32::FindFile qw/SetCurrentDirectory/;

...

SetCurrentDirectory(getcwd());

可以说File::Find在 Win32 上运行时应该这样做。

当然,这只会使“路径名太长”问题变得更糟,因为现在您访问的每个目录都将成为绝对路径的目标SetCurrentDirectory;尝试通过一系列较小的SetCurrentDirectory调用来解决它,你必须想办法回到你来自哪里,当你甚至没有fchdir.

于 2014-04-01T00:07:14.920 回答