Lock.protect 和 callame

use*_*601 10 thread-safety raku

我正在尝试编写一个sub自动线程安全的特征。这就是我所拥有的:

#| A trait to ensure that a sub is not run on multiple threads simultaneously.
multi sub trait_mod:<is> (Sub \code, :$protected!) {

    # If :!protected, do nothing.
    if $protected {

        # Create a new lock outside the multithreaded area
        my $lock = Lock.new;

        # Wrap the sub with a routine that hides callsame in a lock
        code.wrap: sub (|) {
            $lock.protect: {callsame}
        }
    }
}

#| Should print "Start X and finish X" if properly protected
sub needs-protection($x) is protected {
   print "Start $x and ";
   sleep 1;
   say "finish $x";
}

# Test it out.
# If not protected, completes in 1 second with malformed output
(1..4).hyper(:1batch, :4degree) { 
   needs-protection $_
}
Run Code Online (Sandbox Code Playgroud)

然而,AFAICT,似乎callsame没有做任何事情(它返回Nil但就是这样)。我的猜测是它以某种方式试图为 调用不同的候选者.protect,但我没有看到一种方法来确保callsame链接到包装子,而不是其他一些方法。

我能够通过这样做来让它工作

multi sub trait_mod:<is> (Sub \code, :$protected!) {
    if $protected {
        my $lock = Lock.new;
        code.wrap: sub (|c) {
            if CALLERS::<$*PROTECTED> {
                $*PROTECTED = False;
                return callsame;
            }

            $lock.protect: { 
                my $*PROTECTED = True;
                code.CALL-ME(|c);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但这感觉很笨拙,而且我可能遗漏了一些东西,当事情不安全时,它可以让True价值$*PROTECTED滑出。有没有办法callsameprotect-ed 块内直接进行while ?

Jon*_*ton 13

延迟例程,例如callsame寻找最近的动态范围分派以恢复。{callsame}传递给该方法的块protect将被该方法调用protect,并且动态范围内最近的分派将是方法分派到protect。因此,它将尝试遵循protect的基类中的方法Lock。没有,所以Nil结果。

为了解决这个问题,我们需要在正确的动态范围内获取被包装的目标,并使其在词法上可用。这可以使用nextcallee

#| A trait to ensure that a sub is not run on multiple threads simultaneously.
multi sub trait_mod:<is> (Sub \code, :$protected!) {
    # If :!protected, do nothing.
    if $protected {

        # Create a new lock outside the multithreaded area
        my $lock = Lock.new;

        # Wrap the sub with a routine that hides callsame in a lock
        code.wrap: sub (|c) {
            my &target = nextcallee;
            $lock.protect: { target(|c) }
        }
    }
}

#| Should print "Start X and finish X" if properly protected
sub needs-protection($x) is protected {
   print "Start $x and ";
   sleep 1;
   say "finish $x";
}

# Test it out.
# If not protected, completes in 1 second with malformed output
for (1..4).hyper(:1batch, :4degree) { 
   needs-protection $_
}
Run Code Online (Sandbox Code Playgroud)

这给出了我期望您期望的输出。

  • 好吧,我现在觉得很傻。我只能认为我通过将 *nextcallee* 与其他返回调用结果的 *callsame*、*nextwith* 等混在一起来掩盖 *nextcallee*,而不是对下一个调用的引用 - 而且它显然是为我的目的而设计的正在做。谢谢jnthn++! (3认同)