What can be done to kill the stray thead and warn the user that a thread has gone on to la-la-land? Is there a way to terminate this thread?
Threads cannot be
cancelled directly. However, if you install
threads v1.27 or higher (obtain the latest version from CPAN), you can send a signal to a thread so that it can then terminate itself (say by calling
die). Be sure to read the
caveats in the Thread Signalling section of the POD.
Here is a working example that should give you an idea of what you need to do:
#!/usr/bin/perl
use strict;
use warnings;
no warnings 'threads';
use threads 1.27;
use threads::shared;
use Thread::Queue;
### Global Variables ###
# Flag to inform all threads that application is terminating
my $TERM :shared = 0;
# Used for timing out threads
my $TIMEOUT = 10;
my $TIMER = Thread::Queue->new();
# Used by threads to signal that they are done
my $DONE = Thread::Queue->new();
### Signal Handling ###
# Gracefully terminate application on ^C or 'kill'
$SIG{'INT'} = $SIG{'TERM'} =
sub {
print(">>> Terminating <<<\n");
$TERM = 1;
};
# This signal handler is called inside threads
# that get cancelled by the timer thread
$SIG{'KILL'} =
sub {
my $tid = threads->tid();
# Tell user we've been terminated
printf(" %3d <- Killed\n", $tid);
# Tell main thread that we're done
$DONE->enqueue($tid);
# Commit hari-kari
die("Thread $tid killed\n");
};
### Main Processing Section ###
MAIN:
{
# Number of threads
my $max_threads = (@ARGV) ? shift : 10;
# Start timer thread
threads->create('timer')->detach();
# Do lots of work
while (! $TERM) {
# Start max threads
for (my $needed = $max_threads - threads->list();
$needed && ! $TERM;
$needed--)
{
# New thread
my $tid = threads->new(\&worker)->tid();
# Add to timer queue
$TIMER->enqueue($tid, $TIMEOUT);
}
# Join with threads as they finish
while (! monitor_threads()) {
sleep(1);
}
}
# Wait for threads to finish
while ((threads->list() > 0) && $TIMEOUT--) {
sleep(1);
monitor_threads();
}
# Kill and detach remaining threads
foreach my $thr (threads->list()) {
$thr->kill('KILL')->detach();
}
sleep(1); # A moment of silence
}
print("Done\n");
exit(0);
### Thread Entry Point Subroutines ###
# A worker thread
sub worker
{
# My thread ID
my $tid = threads->tid();
printf("Working -> %3d\n", $tid);
# Do some work while monitoring $TERM
my $sleep = 5 + int(rand(10));
while (($sleep > 0) && ! $TERM) {
$sleep -= sleep($sleep);
}
# Tell user we're done
printf(" %3d <- Finished\n", $tid);
# Tell main thread that we're done
$DONE->enqueue($tid);
return;
}
# The timer thread that monitors other threads for timeout
sub timer
{
my %timers; # Contains thread IDs and their remaining time
# Loop until told to quit
while (! $TERM) {
# Check for new threads
while (my $tid = $TIMER->dequeue_nb()) {
# Decrement its timeout
$timers{$tid} = $TIMER->dequeue() - 1;
}
sleep(1);
# Check for timed out threads
while (my ($tid, $timeout) = each(%timers)) {
if ($timeout) {
$timers{$tid}--;
} else {
# Cancel timed out threads, if still running
if (my $thr = threads->object($tid)) {
$thr->kill('KILL');
}
delete($timers{$tid});
}
}
}
}
### Helper Subroutines ###
# Join to threads that have finished processing
# Returns the number of threads joined
sub monitor_threads
{
my $count = 0;
while (my $tid = $DONE->dequeue_nb()) {
threads->object($tid)->join();
$count++;
}
return ($count);
}
Remember: There's always one more bug.