Lock.protect 和 callsame

Lock.protect and callsame

我正在尝试自动将特征写入线程安全 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 $_
}

但是,AFAICT,似乎 callsame 没有做任何事情(它 returns 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);
            }
        }
    }
}

但这感觉很笨拙,我可能遗漏了一些允许 $*PROTECTEDTrue 值在事情不安全时滑出的东西。有没有办法在 protect-ed 块内直接 callsame

callsame 这样的延迟例程寻找最近的 动态范围 调度来恢复。传递给方法 protect 的块 {callsame} 将由 protect 方法调用,动态范围内最近的分派将是对 protect 的方法分派。因此,它将尝试在 Lock 的基础 class 中遵循 protect 方法。没有,因此 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 $_
}

这给出了我期望的输出。