Reputation: 374
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.
Upvotes: 2
Views: 1939
Reputation: 53488
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";
}
Upvotes: 3
Reputation: 150
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...
Upvotes: 2