Re: Why does threads::join block SIGALRM
by BrowserUk (Patriarch) on Jun 07, 2016 at 02:24 UTC
|
See Safe signals for some background to what you've been told elsewhere.
That said, setting PERL_SIGNALS=unsafe won't change anything; and least not on windows. You'll need to try it for yourself on other OSs.
However, the bottom line here is that using an alarm to interrupt join() is a pretty weird thing to want to be doing. If you outline the use-case; there is almost certainly a simpler and more reliable solution.
With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
In the absence of evidence, opinion is indistinguishable from prejudice. Not understood.
| [reply] |
|
|
Agreed, unsafe signals aren't required. I definitely agree that refactoring this code may be the order of the day. The person was attempting to identify when worker threads which were told to finish up work, didn't finish up and a forceful exit occurs. Consider it to be like deadlock avoidance.
I boiled it down to a brain dead example to try and isolate exactly what was going wrong in the code, and ultimately determined it was the join() call. I created the equivalent C code, and found that pthread_join() doesn't block deliver of the SIGALRM. So, assuming that's actually what threads::join() is using (the guy over at SO realized he misspoke saying it was implemented with mutex_lock()), then it seems like I should get the same behavior out of perl. But, I'm assuming there's something with how signals are being delivered, or the layer of indirection in the interpreter calling out to pthread_join() that prevents the signal from being delivered in perl, where it is in C.
| [reply] |
|
|
I created the equivalent C code, and found that pthread_join() doesn't block deliver of the SIGALRM.
The existence of SAFE_SIGNALS in perl means that nothing you do in C is "equivalent"; and no lessons drawn from C can be applied to Perl threads.
It is also unfortunate that when Perl's threads were devised, the authors chose the pthreads api as their base, as that entire api is full of holes, anomalies and sheer bloody-minded justifictions.
For example: a useful pattern of thread usage is a fixed sized thread pool that starts a fixed number of threads doing work, and then replaces the first one to finish with another doing the next unit of work. To enable this, it would be useful to be able to issue a 'block-and-join-the-next-thread-that-finishes()' call; the logical equivalent of:
waitpid(-1, &status, 0);
But the pthreads api docs obnoxiously state: There is no pthreads analog of waitpid(-1, &status, 0), that is, "join with any terminated thread". If you believe you need this functionality, you probably need to rethink your application design.
It's also unfortunate that development of the Perl api hasn't attempted to keep pace with the later revisions of the underlying pthreads api.
For example: If the pthreads api: int pthread_timedjoin_np(pthread_t thread, void **retval, const struct timespec *abstime); was exposed as a part of the Perl threads api, then it would go a long way to addressing your particular problem; and would permit several useful design patterns that are currently either impossible, or require elaborate hand-coding by the user to implement.
The history of threading in Perl is one fraught with short-sighted decisions and prejudices. 10 years ago I wrote The future is threaded which basically got the response: "thread is spelt 'fork'" from many directions.
I'm currently working on speeding up an elaborate piece of scientific C++ code, trying to reduce the run times from 90+ hours by an order of magnitude. I can get over one third of that reduction by simply splitting the major loops across my 4 cores threads. Another 3rd will come from hand-coding the strip-mining, loop-blocking and swizzling needed to use SSE vector operations on the code; which is tedious and laborious. The last third will have to come from careful hand-optimisations and dropping into assembler.
My old 4 core machine has seen better days; almost nothing of the original machine remains and even those parts that I've already replaced -- like the cpu fan -- are beginning to wear out. Long story short, I'm currently specing up a replacement machine based around an 8-core 4Ghz processor for £130; and if I could bring myself to paying through the nose for it I could be looking at a 20(10) core processor.
With the latter machine, I could achieve the full order of magnitude time saving just by adjusting the number of threads.
10 years on, that threaded future is now; and Perl hasn't moved forward at all. So sad.
With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
In the absence of evidence, opinion is indistinguishable from prejudice. Not understood.
| [reply] [d/l] [select] |
|
|
|
|
I should also note that after a lively discussion, it's probably not accurate for me to say that pthread_join() is "interruptable", as the man page doesn't claim that it can be, technically, interrupted (returning EINTR). That being said, it can't be disputed that the C program is "suspending" the pthread_join(), or something, in order to call the ALRM signal handler, and then goes back to waiting in the pthread_join().
| [reply] |
Re: Why does threads::join block SIGALRM
by oiskuu (Hermit) on Jun 07, 2016 at 13:30 UTC
|
If the worker does not share data with the main program, it is probably best to fork it off as a child process.
Threads are expected to behave nice, as they can write shared data, terminate the whole process with an exec, etc. Implementing the blocking/timeout logic in a thread itself should be preferable. In any case, threads i.e. clones share signal dispositions (signal handlers), so extra care should be taken when mixing threads and signals.1 (Don't use alarm.)
That said, you can work around the problem by using Thread::Queue and a baby-sitter.
#! /usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use threads;
use Thread::Queue;
my $Q = Thread::Queue->new();
sub manage {
async{ $Q->enqueue({ $_->tid => $_->join }) }->detach for @_
}
sub worker {
print "Worker started.\n";
# 1 while 1;
return 42;
}
manage( threads->create(\&worker) );
print Dumper $_ for $Q->dequeue_timed(2);
1) Update. Perl will dispatch signals, thus threads can have their separate handlers. But not a mix of 'IGNORE'/'DEFAULT'/code. It also appears that most signals are blocked in threads; to get around this, one might use POSIX::sigprocmask. But it's hopeless anyway: alarm is per-process.
Correction: Just blocking the ALRM in main thread (after thread creation) allows another thread to catch it. I don't know if this can be relied on...
| [reply] [d/l] [select] |
Re: Why does threads::join block SIGALRM
by SimonPratt (Friar) on Jun 07, 2016 at 09:33 UTC
|
#!/usr/bin/env perl
use strict;
use warnings;
use threads;
sub worker {
print "Worker thread started.\n";
while(1){}
}
my $thread = threads->create(\&worker);
print "Setting alarm.\n";
$SIG{ALRM} = sub { die "Alarm!\n" };
alarm 2;
print "Check join\n";
while (threads::running) {
$_->join for threads->list(threads::joinable);
}
Yes, I am aware that this will spin like crazy, wasting a lot of system resources, however you can't sleep to limit this, as sleep is sometimes implemented using the system alarm. Actually thinking about it some more, I suspect that join may have been modified to use the system alarm, hence wiping out the alarm you initially set, causing it to never trigger.
Based on this, a "better" approach could be something like this:
#!/usr/bin/env perl
use strict;
use warnings;
use threads;
use constant TIMEOUT => 2;
sub worker {
print "Worker thread started.\n";
while(1){}
}
my $thread = threads->create(\&worker);
print "Check join\n";
while (1) {
$_->join for threads->list(threads::joinable);
last unless threads->list(threads::running);
die "Alarm!\n" if time - $^T > TIMEOUT;
select(undef,undef,undef,0.5);
}
Update: Updated second code example | [reply] [d/l] [select] |
|
|
Yeah, I refactored the code today to effectively the same thing you suggested in the second code example above. It doesn't have the same "instant" granularity of interrupting the join(), but I would prefer to do it this way than run with unsafe signals. Thanks!
| [reply] |
Re: Why does threads::join block SIGALRM
by marioroy (Prior) on Jun 07, 2016 at 16:43 UTC
|
Hi CDahn,
I tried with MCE::Hobo. A Hobo is a migratory worker inside the machine that carries the asynchronous gene. Hobos are equipped with threads-like capability for running code asynchronously. Unlike threads, each hobo is a unique process to the underlying OS.
use strict;
use warnings;
use MCE::Hobo;
sub worker
{
print "Worker thread started.\n";
while(1){}
}
my $hobo = MCE::Hobo->create(\&worker);
print "Setting alarm.\n";
$SIG{ALRM} = sub { print "Alarm!\n" };
alarm 2;
print "Joining.\n";
$hobo->join();
Output
Setting alarm.
Joining.
Worker thread started.
Alarm!
Kind regards, Mario.
| [reply] [d/l] [select] |
|
|
use strict;
use warnings;
use forks;
sub worker
{
print "Worker thread started.\n";
while(1){}
}
my $proc = threads->create(\&worker);
print "Setting alarm.\n";
$SIG{ALRM} = sub { print "Alarm!\n" };
alarm 2;
print "Joining.\n";
$proc->join();
Output
Setting alarm.
Joining.
Worker thread started.
Alarm!
Kind regards, Mario. | [reply] [d/l] [select] |
Re: Why does threads::join block SIGALRM
by BrowserUk (Patriarch) on Jun 07, 2016 at 02:14 UTC
|
| [reply] |
|
|
| [reply] |