K-s*_*cer 8 oop perl multithreading
Perl新秀在这里,所以请温柔:)
我写了以下代码,以便在我狩猎时跟踪我的狗(不是真的).每当一只狗发现一只鸭子时,它就会向主线发出信号,然后主线从每只狗中收集信息.
#!/usr/bin/env perl
use strict;
use warnings;
use v5.14;
use threads;
{
package Dog;
sub new {
my ($class, $name, $dt) = @_;
my $self = {
dt => $dt, # will find a duck every $dt seconds
name => $name,
ducksfound => 0
};
bless $self, $class;
}
sub hunt {
#
# the "thread" method -- the dog will hang around for $dt seconds,
# then alert the main thread by sending SIGUSR1
#
my $self = shift;
while (1) {
sleep $self->{dt};
$self->{ducksfound} += 1;
kill USR1 => $$;
}
}
sub bark {
my $self = shift;
sprintf "%s: found %d ducks!", ($self->{name}, $self->{ducksfound});
}
1;
}
my @dogs;
$SIG{USR1} = sub {
say join ", ", map { $_->bark } @dogs;
};
push @dogs, Dog->new("Labrador", 1);
push @dogs, Dog->new("Retriever", 2);
push @dogs, Dog->new("Shepherd", 3);
threads->create( sub { $_->hunt } ) for @dogs;
$_->join for threads->list;
Run Code Online (Sandbox Code Playgroud)
上述代码的预期输出将类似于:
拉布拉多:找到1只鸭子!,猎犬:找到0只鸭子!,牧羊人:发现0只鸭子!
拉布拉多:找到2只鸭子!,猎犬:找到0只鸭子!,牧羊人:发现0只鸭子!
拉布拉多:找到3只鸭子!,猎犬:找到0只鸭子!,牧羊人:发现0只鸭子!
拉布拉多:找到3只鸭子!,猎犬:找到1只鸭子!,牧羊人:发现0只鸭子!
拉布拉多:找到4只鸭子!,猎犬:找到1只鸭子!,牧羊人:发现0只鸭子!
拉布拉多:找到5只鸭子!,猎犬:找到1只鸭子!,牧羊人:发现0只鸭子!
拉布拉多:找到6只鸭子!,猎犬:找到1只鸭子!,牧羊人:发现0只鸭子!
拉布拉多:找到6只鸭子!,猎犬:找到1只鸭子!,牧羊人:发现0只鸭子!
拉布拉多:找到6只鸭子!,猎犬:找到1只鸭子!,牧羊人:找到1只鸭子!
相反,我得到的是以下内容:
拉布拉多:找到1只鸭子!,猎犬:找到0只鸭子!,牧羊人:发现0只鸭子!
拉布拉多:找到2只鸭子!,猎犬:找到0只鸭子!,牧羊人:发现0只鸭子!
拉布拉多:找到3只鸭子!,猎犬:找到0只鸭子!,牧羊人:发现0只鸭子!
拉布拉多:发现0只鸭子!,猎犬:找到1只鸭子!,牧羊人:发现0只鸭子!
拉布拉多:找到4只鸭子!,猎犬:找到0只鸭子!,牧羊人:发现0只鸭子!
拉布拉多:找到5只鸭子!,猎犬:找到0只鸭子!,牧羊人:发现0只鸭子!
拉布拉多:发现0只鸭子!,猎犬:找到2只鸭子!,牧羊人:发现0只鸭子!
拉布拉多:发现0只鸭子!,猎犬:找到0只鸭子!,牧羊人:找到1只鸭子!
请注意每只狗的鸭子计数如何重置为零,而另一只狗正在说话.
关于在阅读骆驼时我必须掩饰哪些特定脚注的任何见解?
根本问题在于默认情况下Perl变量不是共享的,它结合了一些关于哪个线程正在服务哪个信号来产生你所看到的结果的怪异.
当你产生狩猎线程时,每个线程都会得到它自己的副本@dogs及其内容.这就是Perl线程的工作方式:解释器及其当前状态 - @dogs,%SIGopen STDOUT- 克隆整个.要了解其工作原理,请考虑以下代码:
my %dog_decls = (
Labrador => 1,
Retriever => 2,
Shepherd => 3,
);
while (my ($name, $delay) = each %dog_decls) {
my $dog = Dog->new($name, $delay);
push @dogs, $dog;
threads->create(sub { $dog->hunt });
}
$_->join for threads->list;
Run Code Online (Sandbox Code Playgroud)
克隆发生在threads->create时间,因此这些线程中的每一个都获得了不同的版本@dogs.因此,Dogs当其中一个捕获鸭子时,那个树皮的列表取决于哪个线程捕获信号!(另请注意,您可以推断each从此输出发出哈希的顺序.)
猎犬:找到0只鸭子!拉布拉多:找到1只鸭子!
猎犬:找到0只鸭子!,拉布拉多:找到2只鸭子!
猎犬:找到1只鸭子!
猎犬:找到0只鸭子!拉布拉多:找到3只鸭子!
猎犬:找到0只鸭子!,拉布拉多:找到4只鸭子!
猎犬:找到0只鸭子!拉布拉多:找到0只鸭子!,牧羊人:找到1只鸭子!
回到你的代码:当Labrador线程(线程1)醒来时,它会更新Labrador's ducksfound并发送一个SIGUSR1.有人(我们会更多地谈论谁在一秒钟内)看到信号和barks所有信号Dogs.但唯一Labrador一个已经改变的是一个在线程1 Retriever和Shepherd线程(线程分别为2和3)还没有看到更新Labrador的ducksfound.
那么为什么ducksfound最初打印的值是正确的呢?由于您安装信号处理程序的方式.你在整个过程中安装它 - 回想一下我说的%SIG是克隆到你的线程中的东西.所以每个线程都有一个处理程序USR1,导致所有的Dogsto bark.当你发送USR1时$$,无论哪个线程在那一刻醒来都会抓住它.发生这种情况的是发送信号的线程是清醒的线程.
这就解释了为什么当Retriever捕获它的第一只鸭子时,它的ducksfound价值是正确的,但事实Labrador并非如此. Retriever捕获线程2中的鸭子,它发送SIGUSR1给自己然后发送barks它Dogs.但是在线程2中,Labrador从未更新过,因此树皮显示为0 Labrador和1为Retriever.
非共享变量的问题可以通过以下方式简单地得到threads::shared:
use threads::shared;
...
my @dogs :shared;
...
push @dogs, shared_clone(Dog->new("Labrador", 1));
Run Code Online (Sandbox Code Playgroud)
现在,当一个线程更新a时Dog,所有线程都会看到它,因此哪个线程正在为信号提供服务并不重要.哪个好,因为在你的代码中,"主线程"(线程0)永远不会得到回控制.这可能没问题,但可能导致比您预期的稍微怪异的行为.
如果您确实希望存在管理器线程,则可能需要显式生成它:
# in Dog::new
my ($class, $name, $hunter, $dt) = @_;
...
hunter => $hunter,
# in Dog::hunt
$self->{hunter}->kill('USR1');
# in main
my $hunter_thread = threads->create(
sub {
local $SIG{USR1} = sub {
say join ", ", map { $_->bark } @dogs;
};
while (1) { usleep 100_000 } # higher resolution than hunt events
}
);
...
push @dogs, shared_clone(Dog->new("Labrador", $hunter_thread, 1));
Run Code Online (Sandbox Code Playgroud)
请注意,只是在没有共享的情况下放入管理器线程Dogs会导致线程被唤醒以打印一堆零.您需要同时执行这两项操作才能获得您期望的结果.