File Coverage

blib/lib/Parallel/Loops.pm
Criterion Covered Total %
statement 230 248 92.7
branch 36 48 75.0
condition 2 3 66.6
subroutine 35 43 81.4
pod 4 9 44.4
total 307 351 87.4


line stmt bran cond sub pod time code
1             package Parallel::Loops;
2              
3             our $VERSION='0.12';
4              
5             # For Tie::ExtraHash - This was the earliest perl version in which I found this
6             # class
7 16     16   2168112 use 5.008;
  16         80  
8              
9             =head1 NAME
10              
11             Parallel::Loops - Execute loops using parallel forked subprocesses
12              
13             =encoding utf-8
14              
15             =head1 SYNOPSIS
16              
17             use Parallel::Loops;
18              
19             my $maxProcs = 5;
20             my $pl = Parallel::Loops->new($maxProcs);
21              
22             my @parameters = ( 0 .. 9 );
23              
24             # We want to perform some hefty calculation for each @input and
25             # store each calculation's result in %output. For that reason, we
26             # "tie" %output, so that changes to %output in any child process
27             # (see below) are automatically transfered and updated in the
28             # parent also.
29              
30             my %returnValues;
31             $pl->share( \%returnValues );
32              
33             $pl->foreach( \@parameters, sub {
34             # This sub "magically" executed in parallel forked child
35             # processes
36              
37             # Lets just create a simple example, but this could be a
38             # massive calculation that will be parallelized, so that
39             # $maxProcs different processes are calculating sqrt
40             # simultaneously for different values of $_ on different CPUs
41             # (Do see 'Performance' / 'Properties of the loop body' below)
42              
43             $returnValues{$_} = sqrt($_);
44             });
45             foreach (@parameters) {
46             printf "i: %d sqrt(i): %f\n", $_, $returnValues{$_};
47             }
48              
49             You can also use @arrays instead of %hashes, and/or while loops
50             instead of foreach:
51              
52             my @returnValues;
53             $pl->share(\@returnValues);
54              
55             my $i = 0;
56             $pl->while ( sub { $i++ < 10 }, sub {
57             # This sub "magically" executed in parallel forked
58             # child processes
59              
60             push @returnValues, [ $i, sqrt($i) ];
61             });
62              
63             And you can have both foreach and while return values so that $pl->share()
64             isn't required at all:
65              
66             my $maxProcs = 5;
67             my $pl = Parallel::Loops->new($maxProcs);
68             my %returnValues = $pl->foreach( [ 0..9 ], sub {
69             # Again, this is executed in a forked child
70             $_ => sqrt($_);
71             });
72              
73             =head1 DESCRIPTION
74              
75             Often a loop performs calculations where each iteration of the loop
76             does not depend on the previous iteration, and the iterations really
77             could be carried out in any order.
78              
79             This module allows you to run such loops in parallel using all the
80             CPUs at your disposal.
81              
82             Return values are automatically transfered from children to parents via
83             %hashes or @arrays, that have explicitly been configured for that sort
84             of sharing via $pl->share(). Hashes will transfer keys that are
85             set in children (but not cleared or unset), and elements that are
86             pushed to @arrays in children are pushed to the parent @array too (but
87             note that the order is not guaranteed to be the same as it would have
88             been if done all in one process, since there is no way of knowing
89             which child would finish first!)
90              
91             If you can see past the slightly awkward syntax, you're basically
92             getting foreach and while loops that can run in parallel without
93             having to bother with fork, pipes, signals etc. This is all handled
94             for you by this module.
95              
96             =head2 foreach loop
97              
98             $pl->foreach($arrayRef, $childBodySub)
99              
100             Runs $childBodySub->() with $_ set foreach element in @$arrayRef, except that
101             $childBodySub is run in a forked child process to obtain parallelism.
102             Essentially, this does something conceptually similar to:
103              
104             foreach(@$arrayRef) {
105             $childBodySub->();
106             }
107              
108             Any setting of hash keys or pushing to arrays that have been set with
109             $pl->share() will automagically appear in the hash or array in the parent
110             process.
111              
112             If you like loop variables, you can run it like so:
113              
114             $pl->foreach( \@input, sub {
115             my $i = $_;
116             .. bla, bla, bla ... $output{$i} = sqrt($i);
117             }
118             );
119              
120             =head2 while loop
121              
122             $pl->while($conditionSub, $childBodySub [,$finishSub])
123              
124             Essentially, this does something conceptually similar to:
125              
126             while($conditionSub->()) {
127             $childBodySub->();
128             }
129              
130             except that $childBodySub->() is executed in a forked child process.
131             Return values are transfered via share() like in L above.
132              
133             =head3 While loops must affect condition outside $childBodySub
134              
135             Note that incrementing $i in the $childBodySub like in this example
136             B:
137              
138             $pl->while( sub { $i < 5 },
139             sub {
140             $output{$i} = sqrt($i);
141             # Won't work!
142             $i++
143             }
144             );
145              
146             Because $childBodySub is executed in a child, and so while $i would
147             be incremented in the child, that change would not make it to the
148             parent, where $conditionSub is evaluated. The changes that make
149             $conditionSub return false eventually I take place outside
150             the $childBodySub so it is executed in the parent. (Adhering to
151             the parallel principle that one iteration may not affect any other
152             iterations - including whether to run them or not)
153              
154             =head3 Optional $finishSub parameter
155              
156             In order to track progress, an optional C<$finishSub> can be provided. It will
157             be called whenever a child finishes. The return value from the C<$conditionSub>
158             is remembered and provided to the C<$finishSub> as a reference:
159              
160             my $i = 0;
161             my %returnValues = $pl->while (
162             sub { $i++ < 10 ? $i : 0 },
163             sub {
164             return ($i, sqrt($i));
165             },
166             sub {
167             my ($i) = @_;
168             printf "Child %d has finished\n", $i;
169             }
170             );
171              
172             =head2 share
173              
174             $pl->share(\%output, \@output, ...)
175              
176             Each of the arguments to share() are instrumented, so that when a
177             hash key is set or array element pushed in a child, this is transfered
178             to the parent's hash or array automatically when a child is finished.
179              
180             B Only keys being set like C<$hash{'key'} = 'value'> and
181             arrays elements being pushed like C will be transfered to
182             the parent. Unsetting keys, or setting particluar array elements with
183             $array[3]='value' will be lost if done in the children. Also, if two different
184             children set a value for the same key, a random one of them will be seen by the
185             parent.
186              
187             In the parent process all the %hashes and @arrays are full-fledged, and you can
188             use all operations. But only these mentioned operations in the child processes
189             make it back to the parent.
190              
191             =head3 Array element sequence not defined
192              
193             Note that when using share() for @returnValue arrays, the sequence of elements
194             in @returnValue is not guaranteed to be the same as you'd see with a normal
195             sequential while or foreach loop, since the calculations are done in parallel
196             and the children may end in an unexpected sequence. But if you don't really
197             care about the order of elements in the @returnValue array then share-ing an
198             array can be useful and fine.
199              
200             If you need to be able to determine which iteration generated what output, use
201             a hash instead.
202              
203             =head2 set_waitpid_blocking_sleep
204              
205             This is about blocking calls. When it comes to waiting for child processes to
206             terminate, Parallel::ForkManager (and hence Parallel::Loops) is between a rock
207             and a hard place. The underlying Perl waitpid function that the module relies
208             on can block until either one specific or any child process terminate, but not
209             for a process part of a given group.
210              
211             This means that the module can do one of two things when it waits for one of
212             its child processes to terminate:
213              
214             B This is the default and involves
215             sleeping between checking whether a process has exited. This is the reason why
216             the above simple examples needlessly all take at least one second. But it is
217             safe, in that other processes can exit safely.
218              
219             B This is faster, but not the default as it
220             is potentialy unsafe.
221              
222             To get the unsafe behavior:
223              
224             $pl->set_waitpid_blocking_sleep(0);
225              
226             All C does is setup I the same behavior in
227             C. See L for a
228             much more thorough description.
229              
230             =head2 Recursive forking is possible
231              
232             Note that no check is performed for recursive forking: If the main
233             process encouters a loop that it executes in parallel, and the
234             execution of the loop in child processes also encounters a parallel
235             loop, these will also be forked, and you'll essentially have
236             $maxProcs^2 running processes. It wouldn't be too hard to implement
237             such a check (either inside or outside this package).
238              
239             =head1 Exception/Error Handling / Dying
240              
241             If you want some measure of exception handling you can use eval in the child
242             like this:
243              
244             my %errors;
245             $pl->share( \%errors );
246             my %returnValues = $pl->foreach( [ 0..9 ], sub {
247             # Again, this is executed in a forked child
248             eval {
249             die "Bogus error"
250             if $_ == 3;
251             $_ => sqrt($_);
252             };
253             if ($@) {
254             $errors{$_} = $@;
255             }
256             });
257              
258             # Now test %errors. $errors{3} should exist as the only element
259              
260             Also, be sure not to call exit() in the child. That will just exit the child
261             and that doesn't work. Right now, exit just makes the parent fail no-so-nicely.
262             Patches to this that handle exit somehow are welcome.
263              
264             =head1 Performance
265              
266             =head2 Properties of the loop body
267              
268             Keep in mind that a child process is forked every time while or foreach calls
269             the provided sub. For use of Parallel::Loops to make sense, each invocation
270             needs to actually do some serious work for the performance gain of parallel
271             execution to outweigh the overhead of forking and communicating between the
272             processes. So while sqrt() in the example above is simple, it will actually be
273             slower than just running it in a standard foreach loop because of the overhead.
274              
275             Also, if each loop sub returns a massive amount of data, this needs to be
276             communicated back to the parent process, and again that could outweigh parallel
277             performance gains unless the loop body does some heavy work too.
278              
279             =head2 Linux and Windows Comparison
280              
281             On the same VMware host, I ran this script in Debian Linux and Windows XP
282             virtual machines respectively. The script runs a "no-op" sub in 1000 child
283             processes two in parallel at a time
284              
285             my $pl = Parallel::Loops->new(2);
286             $pl->foreach( [1..1000], sub {} );
287              
288             For comparison, that took:
289              
290             7.3 seconds on Linux
291             43 seconds on Strawberry Perl for Windows
292             240 seconds on Cygwin for Windows
293              
294             =head2 fork() e.g. on Windows
295              
296             On some platforms the fork() is emulated. Be sure to read perlfork.
297              
298             =head2 Temporary files unless select() works - e.g. on Windows
299              
300             E.g. on Windows, select is only supported for sockets, and not for pipes. So we
301             use temporary files to store the information sent from the child to the parent.
302             This adds a little extra overhead. See perlport for other platforms where there
303             are problems with select. Parallel::Loops tests for a working select() and uses
304             temporary files otherwise.
305              
306             =head1 SEE ALSO
307              
308             This module uses fork(). ithreads could have been possible too, but was not
309             chosen. You may want to check out:
310              
311             When to use forks, when to use threads ...?
312             L
313              
314             The forks module (not used here)
315             L
316              
317             threads in perlthrtut
318             L
319              
320             =head1 DEPENDENCIES
321              
322             I believe this is the only dependency that isn't part of core perl:
323              
324             use Parallel::ForkManager;
325              
326             These are in perl's core:
327              
328             use Storable; # Since perl v5.7.3
329             use IO::Handle; # Since perl 5.00307
330             use Tie::Array; # Since perl 5.005
331             use Tie::Hash; # Since perl 5.002
332              
333             =head1 BUGS / ENHANCEMENTS
334              
335             No bugs are known at the moment. Send any reports to peter@morch.com.
336              
337             Enhancements:
338              
339             Optionally prevent recursive forking: If a forked child encounters a
340             Parallel::Loop it should be possible to prevent that Parallel::Loop instance to
341             also create forks.
342              
343             Determine the number of CPUs so that new()'s $maxProcs parameter can be
344             optional. Could use e.g. Sys::Sysconf, UNIX::Processors or Sys::CPU.
345              
346             Maybe use function prototypes (see Prototypes under perldoc perlsub).
347              
348             Then we could do something like
349              
350             pl_foreach @input {
351             yada($_);
352             };
353             or
354              
355             pl_foreach $pl @input {
356             yada($_);
357             };
358              
359             instead of
360              
361             $pl->foreach(\@input, sub {
362             yada($_);
363             });
364              
365             and so on, where the first suggestion above means global variables (yikes!).
366             Unfortunately, methods aren't supported by prototypes, so this will never be
367             posssible:
368              
369             $pl->foreach @input {
370             yada($_);
371             };
372              
373             An alternative pointed out by the perlmonks chatterbox could be to use
374             L "if I can stand
375             pain".
376              
377             =head1 SOURCE REPOSITORY
378              
379             See the git source on github L
380              
381             =head1 COPYRIGHT
382              
383             Copyright (c) 2008 Peter Valdemar Mørch
384              
385             All right reserved. This program is free software; you can redistribute it
386             and/or modify it under the same terms as Perl itself.
387              
388             =head1 AUTHOR
389              
390             Peter Valdemar Mørch
391              
392             =cut
393              
394 16     16   176 use strict;
  16         32  
  16         496  
395 16     16   64 use warnings;
  16         32  
  16         1504  
396              
397 16     16   80 use Carp;
  16         64  
  16         1488  
398 16     16   15632 use IO::Handle;
  16         164672  
  16         1008  
399 16     16   8576 use IO::Select;
  16         34432  
  16         1072  
400 16     16   16432 use File::Temp qw(tempfile);
  16         370512  
  16         1536  
401 16     16   10832 use Storable;
  16         116976  
  16         1232  
402 16     16   13312 use Parallel::ForkManager;
  16         1063424  
  16         89248  
403              
404             sub new {
405 16     16 0 3249200 my ($class, $maxProcs, %options) = @_;
406 16         272 my $self = {
407             maxProcs => $maxProcs,
408             shareNr => 0,
409             workingSelect => testWorkingSelect(),
410             };
411 16         96 return bless $self, $class;
412             }
413              
414             sub testWorkingSelect {
415 16     16 0 176 my $reader = IO::Handle->new();
416 16         544 my $writer = IO::Handle->new();
417 16 50       4464 pipe( $reader, $writer )
418             or die "Couldn't open a pipe";
419 16         3200 $writer->autoflush(1);
420 16         1024 my $select = IO::Select->new();
421 16         336 $select->add($reader);
422 16         1312 print $writer "test\n";
423              
424             # There should be data right away, so lets not risk blocking if it is
425             # unreliable
426 16         96 my @handles = $select->can_read(0);
427 16         672 my $working = (scalar(@handles) == 1);
428              
429 16         176 close $reader;
430 16         224 close $writer;
431              
432 16         272 return $working;
433             }
434              
435             sub share {
436 64     64 1 17056 my ($self, @tieRefs) = @_;
437 64         112 foreach my $ref (@tieRefs) {
438 64 100       256 if (ref $ref eq 'HASH') {
    100          
439 16         48 my %initialContents = %$ref;
440             # $storage will point to the Parallel::Loops::TiedHash object
441 16         16 my $storage;
442 16         160 tie %$ref, 'Parallel::Loops::TiedHash', $self, \$storage;
443 16         240 %$ref = %initialContents;
444 16         144 push @{$$self{tieObjects}}, $storage;
  16         48  
445 16         16 push @{$$self{tieHashes}}, [$$self{shareNr}, $ref];
  16         64  
446             } elsif (ref $ref eq 'ARRAY') {
447 16         16 my @initialContents = @$ref;
448             # $storage will point to the Parallel::Loops::TiedArray object
449 16         32 my $storage;
450 16         80 tie @$ref, 'Parallel::Loops::TiedArray', $self, \$storage;
451 16         80 @$ref = @initialContents;
452 16         144 push @{$$self{tieObjects}}, $storage;
  16         32  
453 16         16 push @{$$self{tieArrays}}, [$$self{shareNr}, $ref];
  16         48  
454             } else {
455 32         4368 croak "Only unblessed hash and array refs are supported by share";
456             }
457 32         96 $$self{shareNr}++;
458             }
459             }
460              
461             sub in_child {
462 246     246 0 665 my ($self) = @_;
463 246   66     5104 return $$self{forkManager} && $$self{forkManager}->is_child;
464             }
465              
466             sub readChangesFromChild {
467 108     108 0 892 my ($self, $childRdr, $childFinishSub) = @_;
468              
469 108         282 my $childOutput;
470              
471 108 100       835 if ($$self{workingSelect}) {
472 97         1781 local $/;
473 97         5997 $childOutput = <$childRdr>;
474             } else {
475 11         570 my $filename = <$childRdr>;
476 11 50       963 open my $in, $filename
477             or die "Couldn't open $filename";
478 11         65 binmode $in;
479             {
480 11         29 local $/;
  11         153  
481 11         509 $childOutput = <$in>;
482             }
483 11         163 close $in;
484 11         1942 unlink $filename;
485              
486             }
487 108 50       633 die "Error getting result contents from child"
488             if $childOutput eq '';
489              
490 108         2067 my @output;
491 108         643 eval {
492 108         179 @output = @{ Storable::thaw($childOutput) };
  108         3579  
493             };
494 108 50       13274 if ($@) {
495 0         0 die "Error interpreting result from child: $@";
496             }
497 108         411 my $error = shift @output;
498 108         513 my $retval = shift @output;
499              
500 108         185 foreach my $set (@{$$self{tieHashes}}) {
  108         2309  
501 108         365 my ($outputNr, $h) = @$set;
502 108         211 foreach my $k (keys %{$output[$outputNr]}) {
  108         502  
503 108         5065 $$h{$k} = $output[$outputNr]{$k};
504             }
505             }
506 108         379 foreach my $set (@{$$self{tieArrays}}) {
  108         424  
507 108         1250 my ($outputNr, $a) = @$set;
508 108         375 foreach my $v (@{$output[$outputNr]}) {
  108         253  
509 108         2464 push @$a, $v;
510             }
511             }
512 108 50       425 if ($error) {
513 0         0 die "Error from child: $error";
514             }
515 108 50       366 $childFinishSub->()
516             if $childFinishSub;
517 108         491 return @$retval;
518             }
519              
520             sub printChangesToParent {
521 15     15 0 202 my ($self, $error, $retval, $parentWtr) = @_;
522 15         55 my $outputNr = 0;
523 15         55 my @childInfo = ($error, $retval);
524 15         35 foreach (@{$$self{tieObjects}}) {
  15         270  
525 30         804 push @childInfo, $_->getChildInfo();
526             }
527             {
528 15         43 local $SIG{PIPE} = sub {
529 0     0   0 die "Couldn't print to pipe";
530 15         1306 };
531 15 100       365 if ($$self{workingSelect}) {
532 10         583 print $parentWtr Storable::freeze(\@childInfo);
533             } else {
534 5         227 my ($fh, $filename) = tempfile();
535 5         8281 binmode $fh;
536 5         307 print $fh Storable::freeze(\@childInfo);
537 5         1647 close $fh;
538 5         193 print $parentWtr $filename;
539             }
540             }
541             }
542              
543             sub set_waitpid_blocking_sleep {
544 16     16 1 10320 my ($self, $sleep) = @_;
545 16         80 $self->{waitpid_blocking_sleep} = $sleep;
546             }
547              
548             sub while {
549 33     33 1 451 my ($self, $continueSub, $bodySub, $finishSub) = @_;
550 33         72 my @retvals;
551              
552             # This is used if $$self{workingSelect}
553 33         56 my $childCounter = 0;
554 33         66 my $nrRunningChildren = 0;
555 33         538 my $select = IO::Select->new();
556              
557             # Else this is used
558 33         394 my %childHandles;
559              
560 33         2165 my $fm = Parallel::ForkManager->new($$self{maxProcs});
561 33 50       87880 if (exists $self->{waitpid_blocking_sleep}) {
562 33         210 $fm->set_waitpid_blocking_sleep($self->{waitpid_blocking_sleep});
563             }
564 33         204 $$self{forkManager} = $fm;
565 33         66 my %childFinishSubs;
566             $fm->run_on_finish( sub {
567 108     108   25616316 my ($pid) = @_;
568 108 100       721 if ($$self{workingSelect}) {
569 97         1558 $nrRunningChildren--;
570             } else {
571 11         93 my $childRdr = $childHandles{$pid};
572             push @retvals, $self->readChangesFromChild(
573 11         286 $childRdr, $childFinishSubs{$childRdr}
574             );
575 11         388 close $childRdr;
576             }
577 33         381 });
578 33         376 while (my $childData = $continueSub->()) {
579             # Setup pipes so the child can send info back to the parent about
580             # output data.
581 135         9144 my $parentWtr = IO::Handle->new();
582 135         14334 my $childRdr = IO::Handle->new();
583 135 50       12239 pipe( $childRdr, $parentWtr )
584             or die "Couldn't open a pipe";
585 135         557 binmode $parentWtr;
586 135         275 binmode $childRdr;
587 135         4403 $parentWtr->autoflush(1);
588              
589 135 50       28044 if ($finishSub) {
590             $childFinishSubs{$childRdr} = sub {
591 0     0   0 $finishSub->($childData);
592 0         0 };
593             }
594              
595 135 100       478 if ($$self{workingSelect}) {
596             # Read data from children that are ready. Block if maxProcs has
597             # been reached, so that we are sure to close some file handle(s).
598             my @ready = $select->can_read(
599 115 100       1338 $nrRunningChildren >= $$self{maxProcs} ? undef : 0
600             );
601 115         12876 for my $fh ( @ready ) {
602             push @retvals, $self->readChangesFromChild(
603 63         1879 $fh, $childFinishSubs{$fh}
604             );
605 63         317 $select->remove($fh);
606 63         4332 close $fh;
607             }
608             }
609              
610 135         1119 my $pid = $fm->start( ++$childCounter );
611 135 100       426510 if ($pid) {
612             # We're running in the parent...
613 120         5551 close $parentWtr;
614 120 100       1017 if ($$self{workingSelect}) {
615 105         1433 $nrRunningChildren++;
616 105         5670 $select->add($childRdr);
617             } else {
618 15         587 $childHandles{$pid} = $childRdr;
619             }
620 120         33438 next;
621             }
622              
623             # We're running in the child
624 15         994 close $childRdr;
625              
626 15         190 my @retval;
627 15         326 eval {
628 15         503 @retval = $bodySub->();
629             };
630 15         156 my $error = $@;
631              
632 15 50       292 if (! defined wantarray) {
633             # Lets not waste any energy printing stuff to the parent, if the
634             # parent isn't going to use the return values anyway
635 15         76 @retval = ();
636             }
637              
638 15         487 $self->printChangesToParent($error, \@retval, $parentWtr);
639 15         2611 close $parentWtr;
640              
641 15         298 $fm->finish($childCounter); # pass an exit code to finish
642             }
643              
644 18 100       979 if ($$self{workingSelect}) {
645 17         459 while (my @ready = $select->can_read()) {
646 34         82866 for my $fh (@ready) {
647             push @retvals, $self->readChangesFromChild(
648 34         941 $fh, $childFinishSubs{$fh}
649             );
650 34         357 $select->remove($fh);
651 34         2745 close $fh;
652             }
653             }
654             }
655              
656 18         915 $fm->wait_all_children;
657 18         479 delete $$self{forkManager};
658 18         1344 return @retvals;
659             }
660              
661             # foreach is implemented via while above
662             sub foreach {
663 16     16 1 128 my ($self, $varRef, $arrayRef, $sub);
664 16 50       64 if (ref $_[1] eq 'ARRAY') {
665 16         32 ($self, $arrayRef, $sub) = @_;
666             } else {
667             # Note that this second usage is not documented (and hence not
668             # supported). It isn't really useful, but this is how to use it just in
669             # case:
670             #
671             # my $foo;
672             # my %returnValues = $pl->foreach( \$foo, [ 0..9 ], sub {
673             # $foo => sqrt($foo);
674             # });
675 0         0 ($self, $varRef, $arrayRef, $sub) = @_;
676             }
677 16         16 my $i = -1;
678 81     81   198 $self->while( sub { ++$i <= $#{$arrayRef} }, sub {
  81         1218  
679             # Setup either $varRef or $_, if no such given before calling $sub->()
680 5 50   5   65 if ($varRef) {
681 0         0 $$varRef = $arrayRef->[$i];
682             } else {
683 5         22 $_ = $arrayRef->[$i];
684             }
685 5         150 $sub->();
686 16         112 });
687             }
688              
689             package Parallel::Loops::TiedHash;
690 16     16   224 use Tie::Hash;
  16         32  
  16         1312  
691 16     16   128 use base 'Tie::ExtraHash';
  16         32  
  16         15168  
692              
693             sub TIEHASH {
694 16     16   32 my ( $class, $loops, $storageRef ) = @_;
695 16         80 my $storage = bless [ {}, { loops => $loops, childKeys => {} } ], $class;
696 16         32 $$storageRef = $storage;
697 16         48 return $storage;
698             }
699              
700             sub STORE {
701 123     123   3535 my ( $data, $key, $value ) = @_;
702              
703 123         1402 my $hash = $$data[0];
704 123         412 my $extra = $$data[1];
705 123         650 my $loops = $$extra{loops};
706              
707 123 100       606 if ( $loops->in_child() ) {
708 15         5078 $$extra{childKeys}{$key} = $value;
709             }
710              
711             # warn sprintf "Setting $key to $value";
712 123         2514 $$hash{$key} = $value;
713             }
714              
715             sub getChildInfo {
716 15     15   69 my ($self, $outputNr) = @_;
717 15         34 my $extra = $$self[1];
718 15         196 return $extra->{childKeys};
719             }
720              
721             package Parallel::Loops::TiedArray;
722 16     16   12928 use Tie::Array;
  16         25888  
  16         720  
723 16     16   512 use base 'Tie::Array';
  16         48  
  16         11680  
724              
725             sub TIEARRAY {
726 16     16   48 my ( $class, $loops, $storageRef ) = @_;
727 16         80 my $storage = bless { arr => [], loops => $loops, childArr => [] }, $class;
728 16         32 $$storageRef = $storage;
729 16         32 return $storage;
730             }
731              
732 159     159   197078 sub FETCHSIZE { scalar @{ $_[0]->{arr} } }
  159         835  
733 0     0   0 sub STORESIZE { $#{ $_[0]->{arr} } = $_[1] - 1 }
  0         0  
734 90     90   472 sub STORE { $_[0]->{arr}->[ $_[1] ] = $_[2] }
735 180     180   819 sub FETCH { $_[0]->{arr}->[ $_[1] ] }
736 51     51   7668 sub CLEAR { @{ $_[0]->{arr} } = () }
  51         497  
737 0     0   0 sub POP { pop( @{ $_[0]->{arr} } ) }
  0         0  
738 0     0   0 sub SHIFT { shift( @{ $_[0]->{arr} } ) }
  0         0  
739 0     0   0 sub UNSHIFT { my $o = shift; unshift( @{ $o->{arr} }, @_ ) }
  0         0  
  0         0  
740 0     0   0 sub EXISTS { exists $_[0]->{arr}->[ $_[1] ] }
741 0     0   0 sub DELETE { delete $_[0]->{arr}->[ $_[1] ] }
742              
743             sub PUSH {
744 123     123   1590 my $self = shift;
745              
746 123 100       407 if ( $$self{loops}->in_child() ) {
747 15         301 push( @{ $self->{childArr} }, @_ );
  15         312  
748             }
749              
750 123         709 push( @{ $self->{arr} }, @_ );
  123         2147  
751             }
752              
753             sub getChildInfo {
754 15     15   177 my ($self) = @_;
755 15         226 return $self->{childArr};
756             }
757              
758             1;