[% setvar title Thread Programming Model %]
Note: these documents may be out of date. Do not use as reference! |
To see what is currently happening visit http://www.perl6.org/
Thread Programming Model
Maintainer: Steven McDougall <swmcd@world.std.com> Date: 31 Aug 2000 Last Modified: 28 Sep 2000 Version: 4 Mailing List: perl6-language-flow@perl.org Number: 185 Status: Developing Frozen since: v3 Unfrozen since: v4
This RFC describes the programming interface to Perl6 threads. It documents the function calls, operators, classes, methods, or whatever else the language provides for programming with threads.
try
for lock
in boolean contextEvent
for condition variableswait_any
and wait_all
Semaphore
and Queue
Frozen
async
lock
, try
, and unlock
Semaphore
, Event
, and Timer
down into Thread::
Queue
use Thread; $sub = sub { ... }; $thread = new Thread \&func , @args; $thread = new Thread $sub , @args; $thread = new Thread sub { ... }, @args; async { ... }; $result = join $thread; $thread = this Thread; @threads = all Thread; $thread1 == $thread2 and ... Thread::yield(); critical { ... }; # one thread at a time in this block # blocking lock $scalar; lock @array lock %hash; lock ⊂ # non-blocking $ok = lock $scalar; $ok = lock @array $ok = lock %hash; $ok = lock ⊂ unlock $scalar; unlock @array unlock %hash; unlock ⊂ cond_wait $mutex; cond_signal $mutex; cond_broadcast $mutex; Thread::delay($seconds); Thread::alarm($time);
new
Thread
\&func, @argsExecutes func(@args) in a separate thread. The return value is
a reference to the Thread
object that manages the thread.
The subroutine executes in its enclosing lexical context. This means that lexical variables declared in that context may be shared between threads. See RFC 178 for examples.
new
Thread
$sub, @argsnew
Thread
sub
{ ... }, @argsExecutes an anonymous subroutine in a separate thread, passing it
@args. The return value is a reference to the Thread
object that
manages the thread.
The subroutine is a closure. References to variables in its lexical
context are bound when the sub
operator executes. See RFC 178 for
examples.
async
BLOCKExecutes BLOCK in a separate thread. Syntactically, async
BLOCK
works like do
BLOCK. async
creates a Thread
object to manage
the thread, but it does not return a reference to it. If you want the
Thread
object, use one of the new
Thread
forms shown above.
The BLOCK executes in its enclosing lexical context. This means that lexical variables declared in that context may be shared between threads.
this
Thread
Returns a reference to the Thread
object that manages the current
thread.
all
Thread
Returns a list of references to all existing Thread
objects in the
program. This includes Thread
objects created for async
blocks.
join
$threadjoin
$threadBlocks until $thread terminates. May be called repeatedly, by any number of threads.
Returns the last expression evaluated in $thread. This expression is evaluated in list context inside the thread.
If join
is called in list context, it returns the entire list; if
join
is called in scalar context, it returns the first element of
the list.
Evaluates to true iff $thread1 and $thread2 reference the same
Thread
object.
Thread::yield
()Gives the interpreter an opportunity to switch to another thread. The interpreter is not obligated to take this opportunity, and the calling thread may regain control after an arbitrarily short period of time.
critical
is a new keyword. Syntactically, it works like do
.
critical { ... };
The interpreter guarantees that only one thread at a time can execute
a critical
block.
lock
applies a lock to a variable.
In void context, it blocks until it acquires the lock.
In non-void context, it does not block, and returns true
or
false
according as the thread does or does not acquire the lock.
lock
$scalarlock
@arraylock
%hashlock
&subApplies a lock to a variable.
If there are no locks applied to the variable, applies a lock and returns immediately. If there are locks applied by another thread, blocks until there are no locks applied. If there are locks applied by the calling thread, applies another lock and returns immediately.
The lock is automatically removed at the end of the lexical scope in
which the lock
operator executes.
lock
$scalarlock
@arraylock
%hashlock
&subTries to apply a lock to a variable.
If there are no locks applied to the variable, applies a lock and returns true. If there are locks applied by another thread, returns false. If there are locks applied by the calling thread, applies another lock and returns true.
The lock is automatically removed at the end of the lexical scope in
which the lock
operator executes.
unlock
$scalarunlock
@arrayunlock
%hashunlock
&subRemoves a lock from a variable.
If there are locks applied by the calling thread, removes one. If there are locks applied by another thread, does nothing. If there are no locks applied to the variable, does nothing.
unlock
never blocks.
A consequence of these rules is that only one thread at a time may have locks applied to a variable.
cond_wait
$mutex$mutex may be any variable.
$mutex must be lock
ed before calling cond_wait
. cond_wait
atomically unlocks $mutex and blocks waiting for a signal from a
cond_signal
($mutex) or cond_broadcast
($mutex) call.
When a signal from a cond_signal
($mutex) or
cond_broadcast
($mutex) call is received, cond_wait
atomically
unblocks the calling thread and reacquires a lock on $mutex.
cond_signal
$mutexSends a signal to one (arbitrarily chosen) thread that is blocked in
a wait_cond
($mutex) call.
cond_broadcast
$mutexSends a signal to all threads that are blocked in
wait_cond
($mutex) calls.
This only documents the interface to the cond_
xxx calls. Using
condition variables to synchronize threads requires additional code.
See, for example,
uw7doc.sco.com
Thread::delay
$secondsBlocks for $seconds seconds. $seconds may be a floating point number, so this interface supports whatever time resolution the platform provides.
Thread::alarm
$timeBlocks until $time seconds after the epoch. $time may be a floating point number, so this interface supports whatever time resolution the platform provides.
All of these features should be doable if threads are built into Perl.
This interface is an amalgam of
Thread.pm
interface from Perl 5.6.0Here are some issues to consider
Threads are created by
new Thread \&func new Thread sub { ... } async { ... }
We arguably don't need three different ways to create threads. However, the different syntaxes fit into the language in slightly different ways, and I'm not sure which one I'd be willing to give up. The first is the most fundamental; losing it would be a serious inconvenience. Perl generally allows an anonymous subroutine where ever it allows a code ref, so the second also seems appropriate. And the third allows us to create threads with the kind of lightweight syntax that makes Perl such a lucid language.
join
The calling context of join
can't be propagated into the thread,
for several reasons.
join
can be
called repeatedly in different contexts.join
can
return the last expression evaluated in the thread, but it can't
retroactively affect the context in which that expression was evaluated.Not allowing multiple join
s on a thread might help with the first
problem; I can't see any way around the second.
This interface provides the
critical { ... }
construct. In principle, we don't need this: you can do the same thing with scoped locks
sub foo { lock &foo; ... } sub bar { { lock $bar::a; ... } { lock $bar::b; ... } }
Nonetheless, critical sections have several attractive features.
Efficiency matters, because critical sections are used to manage things that are...well...critical. Important, global, high-contention resources like memory managers and process schedulers. Granted, these are poor examples for Perl, but you get the idea.
Whether to implement critical
depends partly on whether serializing
execution of a block of code is common enough to merit its own keyword
and syntax. Threads.pm in Perl 5.6.0 documents a :locked
attribute
for subroutines; given a choice, I'd rather have critical
than
:locked
.
Version 3 of this RFC proposed try
to acquire a lock without
blocking. try
does not block, and returns true
or false
according as the thread does or does not acquire the lock.
It was pointed out that the keyword try
will likely be taken for
exception handling. There are several ways we could avoid conflict
try_lock $mutex and ...
try
down into the Thread::
package.Thread::try $mutex and ...
lock
on its calling context.This RFC currently documents the last alternative.
I found out how to use condition variables. See, for example,
uw7doc.sco.com
You can build events out of condition variables, and a lot of other
things besides. I'm pretty sure you can build wait_any
with
condition variables. It seems like you should be able to build
wait_all
, but I haven't yet figured out how.
In any case, I'd rather have condition variables than a menagerie of other synchronization primitives.
I dropped the I/O section, because you can use condition variables to block on I/O in a controlled fashion
async { connect($sock, $addr); $connected=1; cond_signal($sock); } async { Thread::delay(10); $timed_out=1; cond_signal($sock); } async { <STDIN>; $canceled =1; cond_signal($sock); } lock $sock; cond_wait($sock); $connected and ... $timed_out and ... $canceled and ...
You can build all these from mutexes and condition variables.
I dropped the wait functions from this interface. You can build
wait_any
from condition variables. I'm not sure whether or not you
can build wait_all
. However, a built-in wait_all
function would
be limited to waiting on existing synchronization primitives. This
could make it somewhat rigid, and possibly of limited utility.
die
I dropped the $thread->eval
call from this interface, and didn't
say what happens if a thread die
s. There are several possibilities
join
s it. This has a
certain logic to it, but it suffers from the fact that a program
needn't join
its threads, so it doesn't guarantee that exceptions
will actually be handled.$@
on stderr and exits. This is what C++
does. It ensures that exceptions won't just disappear into the void;
however, it also causes a good deal of anxiety and paranoia, because
any thread can potentially blow your program out of the water. (I
speak from experience here.)$@
when a thread die
s. Returning $@
to join
is probably
the Wrong Thing.==
I dropped $thread->equal
in favor of overloading ==
to
compare threads. This seems more natural, and should be easy to
implement if threads are built into the language.
I dropped thread IDs from the interface. You don't want thread IDs. Thread IDs are an implementation artifact. Carrying around explicit numerical indices isn't the Perl way. They were broken anyway (wrap at 2^32, with no guarantee of uniqueness after that).
I dropped detach
from the interface. Detach is an artifact of
languages that require programmers to manage their own storage. It has
rigorous semantics, there's no going back, and if you get it wrong,
you either leak threads or you crash.
In Perl, detachment is more a state of mind. We have threads, and we
have Thread
objects to manage them. The thread holds a reference on
its Thread
object until it terminates. The Thread
object holds a
reference on its thread as long as the Thread
object exists.
If there are no user-visible references to a Thread
object (i.e.
the only reference on the Thread
object is the one held by the
thread), then the thread is said to be detached. A call to
Thread
->all
or Thread
->this
could recover a reference to
the Thread
object of a detached thread; when this happens, the
thread is no longer detached.
In any case, you don't have to worry about it. Like so many others,
detach
is a problem that Perl doesn't have.
To minimize namespace pollution, we could @EXPORT_OK the functions that appear in this interface.
use Threads qw(yield delay alarm)
On the other hand, if they get moved into the core the issue may be moot.
There are two kinds of timers: relative and absolute. Obviously, you
can always build one kind out of the other, but I wanted to
distinguish them with different constructors. I named the constructors
delay
and alarm
, respectively. These are short, and read fairly
naturally.
this Thread
C++ partisans will get brain freeze reading code like
my $thread = this Thread;
but that's not why I traded in self
for this
. Really, it's not.
I did it because it reads more naturally to me.
RFC 1: Implementation of Threads in Perl
RFC 27: Coroutines for Perl
RFC 31: Subroutines: Co-routines
RFC 47: Universal Asynchronous I/O
RFC 178: Lightweight Threads
Threads.pm
PThreads info page