Problem with enqueuing/dequeuing objects between threads

B

Bryan Balfour

I'm just picking up Perl and have been playing around with Thread and
have hit the buffers with my attempts to pass an object (workUnit)
between threads.

What seems to be happening is that the workUnit is successfully
enqueued in one thread and appears to be dequeued successfully in the
other thread. However the following failure message is output when an
attempt is made to extract data from this work unit:

Can't modify non-lvalue subroutine call at waitthread.pl line 71.

My questions are;

- what does the error message mean in the context of my code? Perl
knows that what was dequeued is an instance of my WorkUnit class (as it
says so in the output from line 42 below) so why can't I call its
methods?

- is the different HASH values in the enqueue and dequeue trying to
tell me something.
I'm guessing here, but I assume Perl defines an object in a hash. In
that case, the instance of WorkUnit class created in 'work thread'
below is defined in the hash at address 0x194fd60, right? (I assume
0x194fd60 is an address in memory). If this is true, where did the
WorkUnit instance at 0x1acea40 that was dequeued come from? Does Perl
create a copy? If so, it no longer points to the original WorkUnit.

- is my code faulty? (Quite likely)

- is my design faulty? Am I trying to do something that the Thread
package just doesn't support, or doesn't support on WindowsXP? (Like
passing objects between threads.)

- Have I hit a bug in the Thread package?

Relevant details follow:

I'm running Perl 5.8.6 under WindowsXP using the following classes:

Thread qw(async);
Thread::Queue;
Thread::Semaphore;
Win32::Event;

Basically, I have two threads, 'wait thread', which is created in the
main application, and 'work thread' which is created in the constructor
of my WorkThread class as follows:

19 my($className);
20 my($queueRef);
21 my($queueFlagRef);
22 my($queueEventRef);
23 my($workThread);
24 sub new
25 {
26 $className = shift(@_);
27 my($self) = {};
28 bless($self, $className);
29 $queueRef = shift(@_);
30 $queueFlagRef = shift(@_);
31 $queueEventRef = shift(@_);
32 $workThread = Thread->new(\&getWork);
33 $workThread->detach();
34 return($self);
35 }

I've another class called WorkUnit containing methods to set and get
'class' and 'data'.

In 'Work Thread' the following code:

111 $workUnit = WorkUnit->new();
112 $workUnit->setClass('X');
113 $workUnit->setData('Y');
114 lock $$queueFlagRef;
115 while(TRUE)
116 {
117 $$queueRef->enqueue($workUnit);
118 printMessage("Enqueued " . ref($workUnit) . " at
$workUnit...");
119 last;
120 }
121 $$queueEventRef->pulse();

produces the message:

08:38:56 WorkThread> Enqueued WorkUnit at
WorkUnit=HASH(0x194fd60)

at line 118.

In 'Wait Thread' the following code:

29 my $workQueue = new Thread::Queue;
30 my($workQueueFlag) = Thread::Semaphore->new(1);
31 my($workQueueEvent) = Win32::Event->new();
32 my($workThread) = WorkThread->new(\$WorkQueue, \$workQueueFlag,
\$workQueueEvent);
33 while(TRUE)
34 {
35 $workQueueEvent->wait();
36 while(TRUE)
37 {
38 lock $workQueueFlag;
39 while($workQueue->pending > 0)
40 {
41 $workUnit = $workQueue->dequeue_nb;
42 printMessage("Dequeued " . ref($workUnit) . " at
$workUnit...");
43 &processWorkUnit($workUnit);
44 }
45 last;
46 }
47 }

produces the message:

08:38:56 Wait Thread> Dequeued WorkUnit at
WorkUnit=HASH(0x1acea40)

at line 42.

In &processWorkUnit, the following code;

66 sub &processWorkUnit (\$)
67 {
68 my($workUnit) = @_;
69 while(TRUE)
70 {
71 if($workUnit->getClass() = 'X')
72 {
73 ......
74 last;
75 }
76 printMessage("Unknown work unit.");
77 last;
78 }
79 }

fails at line 71 with the message:

Can't modify non-lvalue subroutine call at waitthread.pl line 71.

Sorry about the long post.

Regards,

Bryan
 
P

Paul Lalli

Bryan said:
I'm just picking up Perl and have been playing around with Thread and
have hit the buffers with my attempts to pass an object (workUnit)
between threads.

What seems to be happening is that the workUnit is successfully
enqueued in one thread and appears to be dequeued successfully in the
other thread. However the following failure message is output when an
attempt is made to extract data from this work unit:

Can't modify non-lvalue subroutine call at waitthread.pl line 71.

My questions are;

- what does the error message mean in the context of my code?

It means the same thing it would mean in any other context - you
attempted to modify a subroutine call. You can't do that unless you
declare the subroutine to be an lvalue. Copying the relevant line from
the code at the end of the post:
71 if($workUnit->getClass() = 'X')

You are attempting to assign the value 'X' to the method call
$workUnit->getClass(). My guess is that what you *meant* to do is
compare the result of that method call to the value 'X', and test for
equality. For that, you want the 'eq' operator, not the '=' operator.

Read up on all the operators (there's a separate equality operator for
comparing numeric values) in:
perldoc perlop
Perl
knows that what was dequeued is an instance of my WorkUnit class (as it
says so in the output from line 42 below) so why can't I call its
methods?

You have misdiagnosed the meaning of the error message. It did not
tell you can't call the method, it told you you can't assign a value to
the call.
- is the different HASH values in the enqueue and dequeue trying to
tell me something.
I'm guessing here, but I assume Perl defines an object in a hash.

Perl doesn't define objects. Code defines objects, by blessing a
reference into a class. Objects can be created out of hash references,
array references, or even scalar references. Read more about them at
perldoc perltoot

In
that case, the instance of WorkUnit class created in 'work thread'
below is defined in the hash at address 0x194fd60, right? (I assume
0x194fd60 is an address in memory). If this is true, where did the
WorkUnit instance at 0x1acea40 that was dequeued come from? Does Perl
create a copy? If so, it no longer points to the original WorkUnit.

- is my code faulty? (Quite likely)

- is my design faulty? Am I trying to do something that the Thread
package just doesn't support, or doesn't support on WindowsXP? (Like
passing objects between threads.)


Sorry, I don't know enough about Threads and whatever Queue'ing module
you're using to answer the rest of these questions.

Paul Lalli
 
B

Bryan Balfour

Thanks for pointing that out, Paul. I originally had numeric values for
'class' and 'data' so was using '==' in the comparison. Without
thinking I incorrectly changed this to '=' when I changed to using
strings. Like you do, I've changed so many things in attempting to
figure out what's happening.

However, changing &processWorkUnit subroutine to;

66 sub &processWorkUnit (\$)
67 {
68 my($workUnit) = @_;
69 while(TRUE)
70 {
71 if($workUnit->getClass() eq 'X')
72 {
73 ......
74 last;
75 }
76 printMessage("Unknown work unit.");
77 last;
78 }
79 }

now produces the error message:

Use of uninitialized value in concatenation (.) or string at
...../WorkUnit.pm line 42.

Line 42 in WorkUnit.pm is in the getClass method.

39 sub getClass
40 {
41 my($self) = shift(@_);
42 print("getClass entered. class: $class\n"); # Debug
message
43 return($class);
44 }

which confirms to me that the WorkUnit dequeued is definitely not the
one that was enqueued.
 
P

Paul Lalli

Bryan said:
now produces the error message:

Use of uninitialized value in concatenation (.) or string at
..../WorkUnit.pm line 42.

Line 42 in WorkUnit.pm is in the getClass method.

39 sub getClass
40 {
41 my($self) = shift(@_);
42 print("getClass entered. class: $class\n"); # Debug
message
43 return($class);
44 }

which confirms to me that the WorkUnit dequeued is definitely not the
one that was enqueued.

Nothing you've shown us in this code confirms anything of the sort.
They only thing we see here is that you're using a variable called
$class in a string that you never assigned anwhere else (that you've
shown us).

Please reduce this problem to the shortest complete piece of executable
code, as suggested by the Posting Guidelines, so that we can copy and
paste the code you give and duplicate your problem. (That also means
to not include those line numbers on the left-hand-side)

Thank you,
Paul Lalli
 
B

Bryan Balfour

Line 112 $workUnit->setClass('X');

Sorry about the line numbers. I put them in to make it easier for
people to comment on the code. A pain for cut and paste though.

Here's the shortest piece of executable code for ThreadTest.pl,
WorkThread.pm and WorkUnit.pm that reproduces the problem. Running it
on a command prompt in WindowsXP, I get the following:

setClass entered. class: X
Enqueue WorkUnit at WorkUnit=HASH(0x1931f34)
Dequeue WorkUnit at WorkUnit=HASH(0x195ca10)
Use of uninitialized value in concatenation (.) or string at
WorkUnit.pm line 32.
getClass entered. class:
Use of uninitialized value in string eq at threadtest.pl line 40.
Unknown work unit.

ThreadTest.pl

use warnings;
use strict;

use WorkUnit;
use WorkThread;

use Thread qw(async);
use Thread::Queue;
use Thread::Semaphore;
use Win32::Event;

use constant FALSE => 0;
use constant TRUE => 1;

my($workUnit);
my $workQueue = new Thread::Queue;
my($workQueueFlag) = Thread::Semaphore->new(1);
my($workQueueEvent) = Win32::Event->new();
my($workThread) = WorkThread->new(\$workQueue,
\$workQueueFlag,
\$workQueueEvent);

while(TRUE)
{
$workQueueEvent->wait();
while(TRUE)
{
lock $workQueueFlag;
while($workQueue->pending > 0)
{
$workUnit = $workQueue->dequeue_nb;
print("Dequeue " . ref($workUnit) . " at $workUnit\n");
&processWorkUnit($workUnit);
}
last;
}
}

sub processWorkUnit (\$)
{
my($workUnit) = @_;
if($workUnit->getClass() eq 'X')
{
print("Success\n");
}
else
{
print("Unknown work unit.\n");
}
}

WorkThread.pm

package WorkThread;

use warnings;
use strict;

use WorkUnit;
use threads;
use threads::shared;
use Thread;

use constant TRUE => 1;
use constant FALSE => 0;

my($className);
my($thread);
my($queueRef);
my($queueFlagRef);
my($queueEventRef);

sub new
{
$className = shift(@_);
my($self) = {};
bless($self, $className);
$queueRef = shift(@_);
$queueFlagRef = shift(@_);
$queueEventRef = shift(@_);
$thread = Thread->new(\&createWork);
$thread->detach();
return($self);
}

sub createWork
{
my($workUnit) = WorkUnit->new();
&share(\$workUnit);
$workUnit->setClass('X');
lock $$queueFlagRef;
while(TRUE)
{
print("Enqueue " . ref($workUnit) . " at $workUnit\n");
$$queueRef->enqueue($workUnit);
last;
}
$$queueEventRef->pulse();
}

return(TRUE);

WorkUnit.pm

package WorkUnit;

use warnings;
use strict;

use constant TRUE => 1;
use constant FALSE => 0;

my($className);
my($class);

sub new
{
$className = $_[0];
my($self) = {};
bless($self, $className);
return($self);
}

sub setClass
{
my($self) = $_[0];
$class = $_[1];
print("setClass entered. class: $class\n");
return(TRUE);
}

sub getClass
{
my($self) = shift(@_);
print("getClass entered. class: $class\n");
return($class);
}

return(TRUE);
 
B

Bryan Balfour

Cracked it. Although I have shared the WorkUnit I hadn't shared the
data elements within the work unit. Changed WorkUnit.pm to;

package WorkUnit;

use warnings;
use strict;

use threads; # Added
use threads::shared; # Added

use constant TRUE => 1;
use constant FALSE => 0;

my($className);
my $class : shared; # Changed

sub new
{
$className = $_[0];
my($self) = {};
bless($self, $className);
return($self);
}

sub setClass
{
my($self) = $_[0];
$class = $_[1];
print("setClass entered. class: $class\n");
return(TRUE);
}

sub getClass
{
my($self) = shift(@_);
print("getClass entered. class: $class\n");
return($class);
}

return(TRUE);

It seems odd to me to have to include classes 'threads' and
'threads::shared' in a class that doesn't use threads which is why it
has taken me so long to figure it out. What occurred to me was that,
although a WorkUnit was being dequeued, $class was unitialised when I
knew it had been. It dawned on me that perhaps it had been there but
had been destroyed when my instance of WorkUnit went out of scope. (Am
I getting the terminology right here?)

Incidentally, when does my instance of WorkUnit and the $class within
it now get destroyed? Is it automatic and if so at what point in my
code does it occur? If not, what do I have to do to destroy it when
I've finished with it?

My apologies for wasting the time of those who took the trouble of
getting involved. I appreciate your help.

Regards,

Bryan
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,167
Latest member
SusanaSwan
Top