pyr0
pyr0

Reputation: 377

how to access perl objects in threads

I hope someone of you is able to help me with my problem. I tried to access a global shared array of objects during a threaded computation and always get the error "use of uninitialized value" although I can print their hashes.

In addition I can not change my objects, due to working with seqio objects from bioperl.

The following example shows my problem.

Thanks in advance.

object class:

package obj;

use strict;
use warnings;
require Exporter;
our @ISA = qw(Exporter);

sub new(){
    my $class=shift;
    my $this = {};
    $this->{"data"} = ();   
    bless($this,$class);
    return($this);
}

sub getData(){
    my $this=shift;
    return $this->{"data"};
}

sub setData($){
    my $this=shift; 
    $this->{"data"}=shift;
}

Test class:

use strict;
use warnings;
use threads;
use threads::shared;
use obj;

my @objs : shared;
foreach (0..2){
    my $o = obj->new();
    $o->setData($_);
    push @objs, share($o);  
}

my @threads=();
$#threads=3;

my $started;
for (my $i=0; $i<10; $i+=$started){
    $started=0;
    foreach (0..$#threads){
        if (not defined $threads[$_]){
            $threads[$_]=threads->new(\&run,(\@objs));
            $started++; 
        } elsif($threads[$_]->is_joinable()){
            $threads[$_]->join();
            $threads[$_]=threads->new(\&run,(\@objs));
            $started++;
        }           
    }   
}
my $running=1;
while ($running>0) {
    foreach (@threads) {    
        if (defined $_){
            $_->join if ($_->is_joinable());    
        }               
    }
    $running = scalar(threads->list(threads::running));       
}   

sub run($){
    my $objs=shift;

    print $_." " foreach (@{$objs});
#   print $_->getData()." " foreach (@{$objs}); try to access data

    print "\n";     
}

Upvotes: 4

Views: 2189

Answers (1)

Greg Bacon
Greg Bacon

Reputation: 139471

The Bugs and Limitations section of the threads::shared documentation warns

When share is used on arrays, hashes, array refs or hash refs, any data they contain will be lost.

[...]

# Create a 'foo' object
my $foo = { 'data' => 99 };
bless($foo, 'foo');

# Share the object
share($foo);        # Contents are now wiped out
print("ERROR: \$foo is empty\n")
    if (! exists($foo->{'data'}));

Therefore, populate such variables after declaring them as shared. (Scalar and scalar refs are not affected by this problem.)

You lose the data in newly-created objects and set up later uninitialized-variable warnings with

for (0..2) {
    my $o = obj->new();
    $o->setData($_);
    push @objs, share($o);  # wipes out $o
}

Note another warning in the threads::shared documentation.

It is often not wise to share an object unless the class itself has been written to support sharing. For example, an object’s destructor may get called multiple times, once for each thread’s scope exit. Another danger is that the contents of hash-based objects will be lost due to the above mentioned limitation. See examples/class.pl (in the CPAN distribution of this module) for how to create a class that supports object sharing.

The code in obj.pm becomes

package obj;

use strict;
use threads;
use threads::shared;
use warnings;

sub new {
    my $class=shift;
    share(my %this);
    $this{"data"} = ();
    bless(\%this,$class);
    return(\%this);
}

sub getData {
    my $this=shift;
    lock($this);
    return $this->{"data"};
}

sub setData {
    my $this=shift;
    lock($this);
    $this->{"data"}=shift;
}

1;

The changes are

  • Use the threads and threads::shared modules.
  • Remove the unused Exporter incantations.
  • In the constructor, create an empty shared hash and then initialize and bless.
  • Add calls to lock in the accessors.

If you forget to remove the call to share in the loop, you still get all the warnings. Change the loop to

foreach (0..2){
    my $o = obj->new();
    $o->setData($_);
    push @objs, $o;  # already shared!
}

to get the following output.

0 1 2
0 1 2
0 1 2
0 1 2
0 1 2
0 1 2
0 1 2
0 1 2
0 1 2
0 1 2
0 1 2
0 1 2

Upvotes: 2

Related Questions