in reply to New threads->create() syntax
This sounds like a job for a few of my Perl OO 'best practices'.
First, use a hash ref not a list of pairs pretending to be a hash for the new API. It makes distinguishing between the old and new API clearer to both the module user and the module's code. It also leaves the most room for subsequent improvements to the API [ such as allowing ->create( {stackBytes=>32*1024}, subName, @args ) ]. Also, it makes the code generally clearer, including putting things like 'odd number of elements' warrnings where they make more sense.
Second, self-factories. Class-level configuration variables suck. But it is even easier (down-right trivial) to implement great "cascading configuration (defaults)" in Perl OO modules.
For module configuration parameters, the first thing you need is a good set of defaults. Then you need a place to store User A's default configuration choices that might be different from User B's defaults. Then you need to know what configuration to use for creating this next thread, merging the global defaults with the user's overrides to these defaults, with the options given in the call to create().
But, like I said, this is actually trivial to do and even gives the user the ability to have multiple layers of defaults and provides other API benefits.
Just store the parameters in a simple object, such as a hash. Then your global defaults simply become a class-global hash declaration. Users get their own identical object as a 'factory' where they can set whatever defaults they want and then create threads from the configured factory. Then they can make this factory as global or as limitted in scope as their heart desires.
And the object that gets associated with the new thread [the return value from ->create(...)] can be the exact same type of object, just with more data added in.
So it is as simple as:
my $default= { stackBytes => 4*1024, ..., }; sub getDefault { return $default; } sub new { my $us= shift @_; my $opts= shift @_; if( $opts && ! isa($opts,"HASH") ) { $opts= { code => $opts, args => \@_, }; } my $class= ref($us) || $us; my $proto= ref($us) ? $us : $class->getDefault(); my $self= bless { }, $class; my @configKeys= keys %{ $class->getDefault() }; # add error checking to taste here, # but this conveys the idea @$self{ @configKeys }= @$proto{ @configKeys }; @$self{ keys %$opts }= values %$opts; return $self; }
Then the user can say
my $threads= threads->new( { stackBytes => 16*1024, # default ... } ); ... my $worker= $threads->create( ... ); my $deep= $threads->create( { stackBytes => 32*1024 }, ..., ); $threads->setStackBytes( ... ); my @hive= map $threads->create( ... ), ...;
[ Note that new() and create() will be nearly identical and can be completely identical, depending on your API design tastes. ]
Now, if this were Perl 6, then the module user would probably get their first factory via something like:
my $threadFactory= use threads;
which has some nice advantages (like not requiring you to repeat the module name all over, the benefits of which are less obvious in the case of 'threads.pm', but mostly do still apply).
In Perl 5, the best you can do is a little bit more awkward. Now, the above example code means that people can go very traditional like:
require threads; ... my $child= threads->create( \&mySsub );
Or set up their personal defaults:
require threads; my $threads= threads->new( { stackBytes => ..., } );
But I like to make that easier, like:
use threads( Factory => \my $threads, StackBytes => ..., );
Which is pretty easy to do with a simple import() sub.
[ I notice that I've violated my own advice by not passing a hash ref to use threads and I'm unsure whether 'use' is enough of a special case that this departure is worthwhile... ]
- tye
|
|---|