使用Perl ithreads的奇怪变量行为

时间:2013-02-27 13:49:14

标签: multithreading perl variables threadpool

我正在尝试基于略微改变的boss / worker模型实现多线程应用程序。基本上,主线程创建了几个boss线程,每个线程又产生两个工作线程(可能更多)。这是因为boss线程每个都处理一个主机或网络设备,而工作线程可能需要一段时间才能完成他们的工作。

我正在使用Thread::Pool来实现这个概念,到目前为止它运作良好;我也不认为我的问题与Thread::Pool有关(见下文)。非常简化的伪代码

use strict;
use warnings;

my $bosspool = create_bosspool();   # spawns all boss threads
my $taskpool = undef;               # created in each boss thread at
                                    # creation of each boss thread 

# give device jobs to boss threads
while (1) {
  foreach my $device ( @devices ) {
    $bosspool->job($device);
  }

  sleep(1);
}

# This sub is called for jobs passed to the $bosspool
sub process_boss
{
  my $device = shift;

  foreach my $task ( $device->{tasks} ) {
    # process results as they become available
    process_result() while ( $taskpool->results );
    # give task jobs to task threads
    scalar $taskpool->job($device, $task);
    sleep(1); ### HACK ###
  }

  # process remaining results / wait for all tasks to finish
  process_result() while ( $taskpool->results || $taskpool->todo );

  # happy result processing
}

sub process_result
{
  my $result = $taskpool->result_any();

  # mangle $result
}

# This sub is called for jobs passed to the $taskpool of each boss thread
sub process_task
{
  # not so important stuff

  return $result;
}

顺便说一句,我没有使用monitor()例程的原因是因为我必须等待$taskpool中的所有工作完成。现在,除非删除### HACK ###行,否则此代码的效果非常好。如果您没有睡觉,$taskpool->todo()将无法提供正确数量的作业,如果您添加它们或者“快速”收到它们的结果。比如,您总共添加了4个作业,但$taskpool->todo()之后只返回2个作业(没有待处理的结果)。这会产生各种有趣的效果。

好的,所以Thread::Pool->todo()是垃圾,让我们尝试一种解决方法:

sub process_boss
{
  my $device = shift;

  my $todo = 0;

  foreach my $task ( $device->{tasks} ) {
    # process results as they become available
    while ( $taskpool->results ) {
      process_result();
      $todo--;
    }
    # give task jobs to task threads
    scalar $taskpool->job($device, $task);
    $todo++;
  }

  # process remaining results / wait for all tasks to finish
  while ( $todo ) {
    process_result();
    sleep(1); ### HACK ###
    $todo--;
  }
}

只要我保留### HACK ###行,这也可以正常工作。如果没有这一行,此代码将重现Thread::Pool->todo()的问题,因为$todo不仅会减1,而且会减少2甚至更多。

我只用一个boss线程测试了这段代码,所以基本上没有涉及多线程(当涉及到这个子程序时)。 $bosspool$taskpool,尤其是$todo不是:shared,没有副作用,对吗?在这个子程序中发生了什么,它只由一个boss线程执行,没有共享变量,信号量等等?

1 个答案:

答案 0 :(得分:0)

我建议实现'worker'线程模型的最佳方法是使用Thread::Queue。做这样的事情的问题是弄清楚队列何时完成,或者项目是否已经出队以及待处理。

使用Thread::Queue,您可以使用while循环从队列中获取元素,并使用end队列,以便while循环返回undef并退出线程。

因此,您并不总是需要多个“boss”线程,您可以使用多种不同版本的worker和输入队列。我会问为什么你需要一个'boss'线程模型。这似乎没必要。

参考: Perl daemonize with child daemons

#!/usr/bin/perl

use strict;
use warnings;
use threads;
use Thread::Queue;

my $nthreads = 4;

my @targets = qw ( device1 device2 device3 device4 );

my $task_one_q = Thread::Queue->new();
my $task_two_q = Thread::Queue->new();

my $results_q = Thread::Queue->new();

sub task_one_worker {
    while ( my $item = task_one_q->dequeue ) {

        #do something with $item

        $results_q->enqueue("$item task_one complete");
    }
}

sub task_two_worker {
    while ( my $item = task_two_q->dequeue ) {

        #do something with $item

        $results_q->enqueue("$item task_two complete");
    }
}

#start threads;

for ( 1 .. $nthreads ) {
    threads->create( \&task_one_worker );
    threads->create( \&task_two_worker );
}

foreach my $target (@targets) {
    $task_one_q->enqueue($target);
    $task_two_q->enqueue($target);
}

$task_one_q->end;
$task_two_q->end;

#Wait for threads to exit.

foreach my $thr ( threads->list() ) {
    threads->join();
}

$results_q->end();

while ( my $item = $results_q->dequeue() ) {
    print $item, "\n";
}

如果您愿意,可以使用boss线程执行类似的操作 - 您可以按boss创建一个队列,并通过引用传递给工作人员。我不确定它是否必要。