4

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";     
}
pyr0
  • 377
  • 1
  • 14
  • 1
    You shouldn't use prototypes (`sub setData($)`, `sub run($)`, `sub foo()`)if you do not know what they are for. In methods, they don't do anything at all. In non-OOp they probably don't do what you think. – simbabque Nov 16 '12 at 08:57
  • Ok, this is new for me. I will read a bit around. But until now my functions do what they are supposed to do. – pyr0 Nov 16 '12 at 09:11
  • 1
    That might be the case. It's just an advice. ;-) Have a look at http://stackoverflow.com/questions/297034/why-are-perl-5s-function-prototypes-bad and maybe http://modernperlbooks.com/mt/2009/08/the-problem-with-prototypes.html – simbabque Nov 16 '12 at 09:17
  • (1) The code you provide does *not* produce the error you specify. Please show us the smallest possible code that actually errors, and show us where it errors. (2) It is unfortunately difficult to use threads and objects in a straightforward manner in Perl. See [here](http://perldoc.perl.org/threads/shared.html#BUGS-AND-LIMITATIONS) and [here](http://perldoc.perl.org/threads.html#BUGS-AND-LIMITATIONS). – pilcrow Nov 16 '12 at 15:09
  • Just enable the out commanded line, then you will get the error. I read the thread documentation already and yes, the loss of information in shared arrays and hashes is my problem. To solve it, I shared the object hash `sub new(){ my $class=shift; share(my %this); return(bless(\%this,$class)); }` and removed the sharing in the test class. But in my real code I am not able to change the object... – pyr0 Nov 19 '12 at 09:19

1 Answers1

2

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
Greg Bacon
  • 134,834
  • 32
  • 188
  • 245
  • thanks for this very good summary. i figured out by myself, that hashes leads to dataloss and i have to share the hashtable in my object first. but i mentioned above, that i am using objects from bioperl and can not change their sourcecode. actual i store a seqio object in an own shared object as workaround. but thanks again. – pyr0 Dec 03 '12 at 15:35