【问题标题】:safe to access shared data structure from signal handler安全地从信号处理程序访问共享数据结构
【发布时间】:2012-05-15 11:09:35
【问题描述】:

我正在尝试确定从为x86_64-linux-thread-multi 构建的 perl (v5.14.2) 中的信号处理程序访问公共(读取:在处理程序代码和程序的其余部分之间共享)数据结构是否安全,但是目标平台是 solaris11)。

perlipc 的示例代码如下:

use POSIX ":sys_wait_h"; # for nonblocking read
my %children;
$SIG{CHLD} = sub {
    # don't change $! and $? outside handler
    local ($!, $?);
    my $pid = waitpid(-1, WNOHANG);
    return if $pid == -1;
    return unless defined $children{$pid};
    delete $children{$pid};
    cleanup_child($pid, $?);
};
while (1) {
    my $pid = fork();
    die "cannot fork" unless defined $pid;
    if ($pid == 0) {
        # ...
        exit 0;
    } else {
        $children{$pid}=1;
        # ...
        system($command);
        # ...
   }
}

所以,%children 是从 while 循环和处理程序中访问的。这似乎没有问题:

  1. 不会有两个进程具有相同的pid
  2. 访问权限由pid 控制(不过,我不确定$childer{pid}=1 是否是原子的且可中断而不会导致损坏。)

现在,我正在尝试在我的处理程序中做更多事情:

my %categoryForPid;
my %childrenPerCategory;

$SIG{CHLD} = sub {
    # ... acquire pid like above
    my $category = $categoryForPid{$pid};
    $childrenPerCategory{$category}--;
    delete $categoryForPid{$pid};
}

while (1) {
    # ... same as above
    } else {
        $children{$pid}=1;
        my $category = # ... chose some how
        $childrenPerCategory{$category}++;
        $categoryForPid{$pid} = $category;
        # ...
    }
}

这里的想法是:每个孩子都属于某个类别(N 到 1)。我想跟踪每个类别存在多少孩子。该信息可能来自$categoryForPid,但我认为这也可能有问题(例如,当进行计算的子程序在总结时被中断)。

所以我的问题是:

  • 我需要在这里同步吗?

顺便说一句:

  • 在 perl 5.12 中是否可以嵌套调用信号处理程序,或者它们是否由解释器线性化?

更新

除了@goldilocks 发现的问题和他提出的解决方案,我现在在更新数据结构以确保“原子性”的同时阻止信号:

my $sigset = POSIX::SigSet->new(SIGCHLD);

sub ublk {
    unless (defined sigprocmask(SIG_UNBLOCK, $sigset)) {
        die "Could not unblock SIGCHLD\n";
    }
}

sub blk {
    unless (defined sigprocmask(SIG_BLOCK, $sigset)) {
        die "Could not block SIGCHLD\n";
    }
}

while (1) {
    # ... same as above
    } else {
         blk;
         $children{$pid}=1;
         my $category = # ... chose some how
         $childrenPerCategory{$category}++;
         $categoryForPid{$pid} = $category;
         ublk;
         # ...
    }
}

【问题讨论】:

    标签: perl signal-handling


    【解决方案1】:

    对我来说似乎是个坏主意。 IPC::Semaphore 可能解决这个问题,如果你能让它们在信号处理程序中正常工作——如果控制在处理程序退出之前没有返回,你就是运气不好。但是,您可以通过锁定父项并让子项等待锁定直到初始化完成来解决此问题;处理程序不涉及信号量。我想你实际上只需要一把锁。无论如何:

    my @array = (1..10);
    my $x = 'x';
    
    $SIG{'USR1'} = sub {
        print "SIGUSER1\n";
        undef @array;
        $x = '!!';
    };
    
    print "$$\n";
    
    foreach (@array) {
        print "$_:\n";
        sleep(2);
        print "\t$x\n";
        print "\t$array[$_ - 1]\n";
    }
    

    不出所料,这样做:

    2482
    1:
        x
        1
    2:
        x
        2
    3:
    SIGUSER1
        !!
    Use of uninitialized value within @array in concatenation (.) or string at ./test.pl line 42.
    

    暗示如果你此时捕捉到信号:

        my $category = # ... chose some how
    

    $categoryForPid{$pid} 在处理程序中将不存在。等等。也就是说,是的,你必须同步。

    【讨论】:

    • 让我试着理解你在说什么: 1. 你的例子告诉我删除处理程序中的 整个 数组会对主代码产生影响。是的我同意。但我只删除一个条目。 2. 我也同意,孩子至少需要活这么久才能让主代码完成数据结构的管理。感谢有关 perl 食谱的提示,我现在知道了,但 freely available version 似乎有点过时了。见perlipc#safesignals
    • @sschober “只删除一个条目” 就像“我没有掷 那么多 骰子”。您的代码中的问题与调用处理程序时可能不存在的内容有关。 WRT 的 Cookbook 已经过时了,没错,但这确实意味着你必须找到证据证明在 perl 5 的更高版本中事情发生了重大变化,因为肯定 mallocprintf(C 库调用)没有。 OTOH,尤其是这两个不应该很难避免。我不太确定信号量是否可以在信号处理程序中工作,顺便说一句,我会添加一个关于它的编辑。
    • 您认为在关键部分期间阻止 SIGCHLD 可能是一种选择吗?
    • @sschober 这不会搞砸逻辑吗?信号量避免在处理程序中使用它们的另一种可能性是父母锁定,派生孩子,孩子做的第一件事是等待锁定,父母完成初始化并解锁。这样孩子就不能提前终止。我认为您实际上可以通过这种方式摆脱单个 sem,只要孩子也立即释放。
    • 不,我不认为阻塞信号会破坏语义,因为这里只涉及一个线程。我猜,使用信号量会产生死锁。锁将在主循环中获取,处理程序将尝试再次获取锁,然后我们将永远挂起。阻塞信号 OTOH 没有问题,因为阻塞的信号似乎要排队等待稍后传递。同一信号的多次出现似乎只是一次出现。这对我来说没问题,因为我使用waitpid(-1,...) 检测到这些情况。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-10
    • 2011-12-30
    • 2012-01-19
    • 2016-03-31
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    相关资源
    最近更新 更多