GetAttributes在子线程中使用错误的工作目录

问题描述:

我使用File::Find来遍历目录树和Win32::FileGetAttributes函数来查看在其中找到的文件的属性。这工作在一个单线程程序。GetAttributes在子线程中使用错误的工作目录

然后我把目录遍历到一个单独的线程,它停止工作。 GetAttributes在“系统找不到指定的文件”的每个文件上都失败,作为$^E中的错误消息。

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

为了说明问题,这里是它会在当前目录中的文件的脚本,在子目录另一个文件,CHDIR对子目录,并查找文件3种方式:system("dir")openGetAttributes

当脚本不带参数运行,dir显示子目录,open发现在子目录中的文件,并GetAttributes成功返回其属性。当与--thread一起运行时,所有测试均以子线程完成,并且diropen仍然有效,但GetAttributes失败。然后它调用GetAttributes对原始目录(我们已经chdir'ed了)的文件,它找到了一个!不知怎的,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(); 
} 

最后,我想通了,是用perl保持某种只适用于perl的内置运营商二次CWD,而GetAttributes是使用原生的CWD。我不知道它为什么这样做,或者为什么它只发生在辅助线程中;我最好的猜测是,perl试图模拟每个进程的一个cwd的unix规则,并且因为模块不兼容而失败。

不管是什么原因,有可能迫使本土CWD是一样的Perl的CWD,每当你即将做Win32::*操作,这样来解决它:

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

... 

SetCurrentDirectory(getcwd()); 

按理说File::Find应在Win32上运行时执行此操作。

当然这只会让“路径名太长”的问题变得更糟,因为现在您访问的每个目录都将成为绝对路径的目标,即SetCurrentDirectory;尝试通过一系列较小的SetCurrentDirectory调用来解决此问题,并且您必须找出一种方法来找回来自哪里,当您甚至没有fchdir时很难。