Search code examples
multithreadingperlreferencesubroutine

Passing subroutine references to perl threads


I am trying to pass subroutine references to perl threads to implement a threadMap subroutine. I am in an environment where I have to 'roll my own' of most things; installing new perl packages is not an option.

Also, I am running perl version 5.10. In my work environment, versions of perl > 5.10 are not available.

I can pass subroutine references around without trouble. However, once I try to pass a subroutine reference to a thread, the thread does not appear to understand it.

Here is my proposed threadMap subroutine, whose comments are, I believe, sufficiently explanatory for interested question-answerers.

#input: hash with keys L (listref), f (function which can apply to each element of L), and optionally nThreads
#default for nThreads is 50
#divides L into sublists for each thread, then kicks off threads
#each thread applies f to each element of the sublist
#and then returns the result of $f on each item
#output: map{ &$f($_) } @{$L}, but done threadily
sub threadMap{
  my %arg = @_;
  my ($L,$f,$nThr) = ($arg{L},$arg{f},$arg{nThreads});
  my $MAXTHREADS = 50;
  if(not defined $nThr or $nThr > $MAXTHREADS){
   $nThr = $MAXTHREADS;
  }

  &log(1,"threadMap: I have f $f");

  my @threadLists = &makeSublistsForThreads($L,$nThr);
  #in the event that L is less than $nThr, we reduce the number of threads
  $nThr = scalar(@threadLists);
  my @threads; 
  my @ret;
  for(0 .. $nThr-1){
  #invoke the threads in list context
  #    push @threads, threads->create({'context' => 'list'}, sub{ my ($L,$f) = @_; print "I have L <@$L> and f $f\n"; return (map{ &f($_) } @{$L}) }, ($threadLists[$_],$f) );
  push @threads, threads->create({'context' => 'list'}, sub{ my ($L) = @_; print "I have L <@$L> and f $f\n"; return (map{ &f($_) } @{$L}) }, ($threadLists[$_]) );
  }
  for(@threads){
  #each thread returns its items, so we get them back in order
      push @ret, $_->join();
  }
  return @ret;
}

When I run this a script called 'foo' which has approximately the following:

my @L = (1 .. 5); 
my $f = sub{ my ($i) = @_; return 100*$i; };

print "I have f $f\n";

@out = &threadMap("L"=>\@L,"f"=>$f);
&log(1,"I had input <@L> and output <@out>");

my @realOut = map{ &$f($_) } @L; 
&log(1,"Output should be <@realOut>");

I get this output:

I have f CODE(0xbf3530)

Wed Jul 17 10:27:49 2013: threadMap: I have f CODE(0xbf3530)

I have L <1> and f CODE(0x110f100)

Thread 1 terminated abnormally: Undefined subroutine &main::f called at /u/jamie /perl/jdPerlLib.pl line 6037.

I have L <2> and f CODE(0x16b3df0)

Thread 2 terminated abnormally: Undefined subroutine &main::f called at /u/jamie /perl/jdPerlLib.pl line 6037.

I have L <3> and f CODE(0x1a7d7b0)

Thread 3 terminated abnormally: Undefined subroutine &main::f called at /u/jamie/perl/jdPerlLib.pl line 6037.

I have L <4> and f CODE(0x1fbb600)

Thread 4 terminated abnormally: Undefined subroutine &main::f called at /u/jamie/perl/jdPerlLib.pl line 6037.

I have L <5> and f CODE(0x7fd5240b78c0)

Thread 5 terminated abnormally: Undefined subroutine &main::f called at /u/jamie/perl/jdPerlLib.pl line 6037.

Wed Jul 17 10:27:49 2013: I had input <1 2 3 4 5> and output <>

This tells me that the function reference is constant from foo to the top of my threadMap subroutine, but once it is passed to the threads it gets changed and goes haywire. Why is this? Can I avoid it?

Note that it fails with both

  #invoke the threads in list context
  #    push @threads, threads->create({'context' => 'list'}, sub{ my ($L,$f) = @_; print "I have L <@$L> and f $f\n"; return (map{ &f($_) } @{$L}) }, ($threadLists[$_],$f) );
  push @threads, threads->create({'context' => 'list'}, sub{ my ($L) = @_; print "I have L <@$L> and f $f\n"; return (map{ &f($_) } @{$L}) }, ($threadLists[$_]) );

and

  #invoke the threads in list context
      push @threads, threads->create({'context' => 'list'}, sub{ my ($L,$f) = @_; print "I have L <@$L> and f $f\n"; return (map{ &f($_) } @{$L}) }, ($threadLists[$_],$f) );
  #push @threads, threads->create({'context' => 'list'}, sub{ my ($L) = @_; print "I have L <@$L> and f $f\n"; return (map{ &f($_) } @{$L}) }, ($threadLists[$_]) );

Also, as this is my first post to stackoverflow, was my question clear enough, or too/insufficiently verbose?


Solution

  • You are calling a function named "f" inside threadMap:

    map{ &f($_) } ...
    

    which function, as the error message indicates, does not exist.

    You mean to dereference and invoke the CODEref instead:

    map { $f->($_) } ...
    

    In your update comment, the code works because there you do define a sub named "f".

    As an aside, you generally should not call perl subs with the ampersand sigil. This is hold-over syntax from perl 4, and has very specific semantics in perl 5 that are almost never needed when you merely want to call a sub.

    Calling &f will disable prototype handling and can pass along @_ — if that behavior doesn't seem immediately useful to you, then don't use that feature. & is also used when denoting the sub per se (e.g., my $coderef = \&f or defined &f) and for special goto &f invocations. & also dereferences a CODE ref, typically to invoke it, but that operation is more obviously conveyed with an arrow and parentheses: $coderef->().