2

My script has hash of objects - All of a single class (SampleClass.pm). Following is the way that I used to create objects.

$objectHash{'foo'} = SampleClass->new();
$objectHash{'bar'} = SampleClass->new();
.
.

Then I spawn few threads (say 5) and each threads does its work as instructed to.

Now say Thread 1 writes to an object -

$objectHash{'foo'}->settimeWhenISaidHello($time);

and exits. Now when Thread2 takes up the work and checks for a value like below

$lastHelloTime = $objectHash{'foo'}->gettimeWhenISaidHello($time);

it gets undefined or empty value. I would like to share such values across threads. How is it possible?

Additionaly, my class constructor new has hashes and array of hashes as member variables like below.

sub new
{
.
.
listOfGuysToSayHello = {}; #This is an array with guy name as key and array value as data
SchoolsWithStudentsToSayHellow = {}; #this is array of hashes
.
.
}

Further more I have already gone through this question - how to access perl objects in threads and the answer did not satisy my requirements.

Please let me know your thoughts.

Bala Krishnan
  • 374
  • 3
  • 18
  • 1
    [`threads::shared`](http://search.cpan.org/~jdhedden/threads-shared-1.57/lib/threads/shared.pm) – Borodin Nov 09 '17 at 10:34

2 Answers2

3

I would suggest that sharing an object in the first place is not a good idea - it can be done with threads::shared but there's some limitations, and you risk introducing some race conditions.

So instead, I would say - use Thread::Queue to communicate between threads, and use Storable to serialize with freeze and thaw.

This is a somewhat simplistic example, but hopefully illustrates the point? Which is that you're not "sharing" objects, but rather just passing them around as serialised data structures. It does breakdown somewhat if instantiating an object does system state changes (like opening a connection to a database or similar) - but that's something that is sort of inherently going to be difficult to do in a threading context.

#!/usr/bin/env perl

use strict;
use warnings;

package MyObject;

sub new {
   my ( $class, %args ) = @_;
   my $self = \%args;
   bless $self, $class;
   return $self;
}

sub get_value {
   my ( $self, $key ) = @_;
   return $self->{$key} // 0;
}

sub set_value {
   my ( $self, $key, $value ) = @_;
   $self->{$key} = $value;
}

package main;

use threads;
use Storable qw ( freeze thaw );
use Thread::Queue;

my $work_q   = Thread::Queue->new;
my $result_q = Thread::Queue->new;

sub worker {
   while ( my $serialised = $work_q->dequeue ) {
      my $local_obj = thaw $serialised;
      print threads->self->tid, " is processing object with id ",
        $local_obj->get_value('id'), "\n";
      $local_obj->set_value( 'processed_by', threads->self->tid );
      $result_q->enqueue( freeze $local_obj );
   }
}

threads->create( \&worker ) for 1 .. 10;

for ( 1 .. 100 ) {
   my $obj = MyObject->new( id => $_ );
   $work_q->enqueue( freeze $obj );
}
$work_q->end;
$_->join for threads->list;

while ( my $ser_obj = $result_q->dequeue_nb ) {
   my $result_obj = thaw $ser_obj;
   print "Object with ID of :", $result_obj->get_value('id'),
     " was processed by thread ", $result_obj->get_value('processed_by'),
     "\n";
}
Sobrique
  • 52,974
  • 7
  • 60
  • 101
2

This happens because Perl doesn't natively share objects - or anything, really - between threads. When Thread2 starts, it gets a copy of the state of the program at that time, and it goes from there. So if Thread1 alters any value after Thread2 was created, this change will only be visible to Thread1.

Now, there are a few ways around that. As @Borodin mentioned, you can use an external library to handle the data exchange for you. If you don't want to depend on an external library, or if you can't install it for some reason, you can do it yourself by using something between them to share this data - like a temporary system file, or a database. It's not as complicated as it sounds like, we have implemented that on my company, and it works just fine.

EDIT: as cleverly pointed out by @PerlDuck below, threads::shared has been a core Perl module for quite a while now...

Jorge_Freitas
  • 175
  • 1
  • 12
  • 2
    `threads::shared` is a core module since Perl 5.7.3. It cannot share "too complicated" things but the docs are quite clear what is possible and what isn't. – PerlDuck Nov 09 '17 at 11:36
  • ... now THAT I didn't know! I guess this is what happens when your company is stuck with the same Perl version for over a decade... We have just updated our systems to use RHEL 7 and in fact `threads::shared` is installed by default. Thanks for pointing that out. – Jorge_Freitas Nov 09 '17 at 11:50
  • In case you wonder how I knew so precisely: together with Perl comes the script [`corelist`](http://search.cpan.org/~bingos/Module-CoreList-5.20171020/corelist). It tells which module is/was a core modules since/until when etc. Try `corelist threads::shared` and `corelist --man`. :-) – PerlDuck Nov 09 '17 at 12:01
  • I have read `thread::shared` module completely and I know what it can do and what it cannot. I wanted to know is there a trick or technique to share something little complicated such as array of hash in objects. BTW i am using perl 5.26.1 – Bala Krishnan Nov 09 '17 at 12:05
  • 1
    @BalaKrishnan One trick would be to serialize your datastructure as a string (e.g. JSON, Data::Dumper), share the string, and upon access de-serialize it. Slow and cumbersome, but will work. – PerlDuck Nov 09 '17 at 12:09
  • @PerlDuck Innovative! I will give a try and let you know! Thanks! Let me know if you hit any other idea! – Bala Krishnan Nov 09 '17 at 12:27
  • 1
    Don't forget `Storable` as an option for serialisation - `freeze` `thaw` work very nicely with `Thread::Queue`. - it actually handles objects quite well, although it's not nearly as portable as JSON. (e.g. not binary portable, and might have problems cross platform) – Sobrique Nov 10 '17 at 10:42