我试图自动编写一个trait来threadsafe一个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
没有做任何事情(它返回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);
}
}
}
}
但是感觉很模糊,我可能错过了一些东西,允许$*PROTECTED
的True
值在事情不安全时滑落。有没有一种方法可以在protect
-ed块内部直接创建callsame
?
像callsame
这样的延迟例程查找最近的动态作用域调度来恢复。传递给方法protect
的块{callsame}
将被protect
方法调用,动态范围内最近的调度将是对protect
的方法调度。因此,它将尝试遵从Lock
基类中的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 $_
}
这给出了我期望你所期望的输出。