使用多线程时,我在哪里必须取消定义队列

时间:2014-06-03 12:00:50

标签: multithreading perl queue

我有一个脚本可以创建一个队列,还有一些正在从队列中读取作业的工作人员。我现在的问题是脚本没有终止并调用printData(),因为线程是空闲的。这是因为我没有将队列设置为undef。

我尝试了很多不同的方法,但都会导致各种问题。

  • 虽然队列中仍有作业
  • ,但队列都已终止
  • 或者目前队列中没有任何作业,尽管仍有一个线程正在工作并试图将新工作推入队列。

我使用以下代码

# -------------------------
# Main
# -------------------------
my @threads = map threads->create(\&doOperation), 1 .. $maxNumberOfParallelJobs;
pullDataFromDbWithDirectory($directory);
#$worker->enqueue((undef) x $maxNumberOfParallelJobs);
$_->join for @threads;

sub pullDataFromDbWithDirectory {
    my $_dir = $_[0];

    if ($itemCount <= $maxNumberOfItems) {
        my @retval = grep { /^Dir|^File/ } qx($omnidb -filesystem $filesystem  '$label'  -listdir '$_dir');

        foreach my $item (@retval) {
            $itemCount++;
            (my $filename = $item) =~ s/^File\s+|^Dir\s+|\n//g;
            my $file = "$_dir/$filename";
            push(@data,$file);

            if ($item =~ /^Dir/) {
                $worker->enqueue($file);
                print "Add $file to queue\n" if $debug;
            }
        }
    }
}

sub doOperation () {
    my $ithread = threads->tid();
    do {
       my $folder = $worker->dequeue();
       print "Read $folder from queue with thread $ithread\n" if $debug;
       pullDataFromDbWithDirectory($folder);
   } while ($worker->pending());

   push(@IDLE_THREADS,$ithread);

}

编辑:

我找到了一个丑陋的解决方案。也许有更好的?我将工人添加到IDLE数组中并一直睡到所有工人都在那里

sleep 0.01 while (scalar @IDLE_THREADS < $maxNumberOfParallelJobs);
$worker->enqueue((undef) x $maxNumberOfParallelJobs);
$_->join for @threads;

1 个答案:

答案 0 :(得分:2)

您不能在没有线程过早死亡的情况下使用->pending()。修正:

my $busy: shared = $num_workers;

sub pullDataFromDbWithDirectory {
    my $tid = threads->tid();
    while (defined( my $folder = $q->dequeue() )) {
        { lock $busy; ++$busy; }
        print "Worker thread $tid processing folder $folder.\n" if $debug;
        pullDataFromDbWithDirectory($folder);
        { lock $busy; --$busy; }
    }

    print "Worker thread $tid exiting.\n" if $debug;
}

sleep 0.01 while $q->pending || $busy;
$worker->end();
$_->join for @threads;

但这引入了竞争条件。

  1. 工作线程将队列中当前的最后一个项目列为
  2. 主线程检查挂起(错误)
  3. 主线程检查忙线程数(无)
  4. 主线程告知工人结束
  5. 所有其他工作线程退出。
  6. 将上面的项目队列化的工人标志着自己忙碌
  7. 工作人员开始处理最后一项,尝试在队列中添加一堆项目并失败。
  8. 出列加上繁忙的递增需要是原子的,挂起的检查加上繁忙的检查需要是原子的。

    如果不改变Thread :: Queue,那是不可能做到的。你不能只是锁定这两段代码,因为这会阻止主机在其中一个空闲时检查所有线程是否空闲。

    我们需要将->dequeue拆分为等待组件及其出列组件。我们有后者(->dequeue_nb),所以我们只需要前者。

    use Thread::Queue 3.01;
    
    sub T_Q_wait {
        my $self = shift;
        lock(%$self);
        my $queue = $$self{'queue'};
    
        my $count = @_ ? $self->_validate_count(shift) : 1;
    
        # Wait for requisite number of items
        cond_wait(%$self) while ((@$queue < $count) && ! $$self{'ENDED'});
        cond_signal(%$self) if (@$queue);
    
        return !$$self{'ENDED'};
    }
    

    现在我们可以编写解决方案了:

    my $busy: shared = 0;
    
    sub pullDataFromDbWithDirectory {
        my $tid = threads->tid();
    
        WORKER_LOOP:
        while (T_Q_wait($q)) {
            my $folder;
    
            {
                lock $busy;
                $folder = $q->dequeue_nb();
                next WORKER_LOOP if !defined($folder);
                ++$busy;
            }
    
            print "Worker thread $tid processing folder $folder.\n" if $debug;
            pullDataFromDbWithDirectory($folder);
    
            {
                lock $busy;
                --$busy;
                cond_signal($busy) if !$busy;
            }
        }
    }
    
    {
        lock $busy;
        cond_wait($busy) while $busy;
        $q->end();
        $_->join() for threads->list();
    }
    

    如果另一个帖子在nextwait之间阻碍了工作,则会dequeue_nb