Velocity Reviews

Velocity Reviews (http://www.velocityreviews.com/forums/index.php)
-   Perl Misc (http://www.velocityreviews.com/forums/f67-perl-misc.html)
-   -   Problem with enqueuing/dequeuing objects between threads (http://www.velocityreviews.com/forums/t893463-problem-with-enqueuing-dequeuing-objects-between-threads.html)

Bryan Balfour 07-27-2005 10:09 AM

Problem with enqueuing/dequeuing objects between threads
 
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


Paul Lalli 07-27-2005 01:18 PM

Re: Problem with enqueuing/dequeuing objects between threads
 
Bryan Balfour wrote:
> 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


Bryan Balfour 07-27-2005 04:23 PM

Re: Problem with enqueuing/dequeuing objects between threads
 
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.


Paul Lalli 07-27-2005 04:57 PM

Re: Problem with enqueuing/dequeuing objects between threads
 
Bryan Balfour wrote:
> 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


Bryan Balfour 07-28-2005 12:03 AM

Re: Problem with enqueuing/dequeuing objects between threads
 
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);


Bryan Balfour 07-28-2005 12:08 PM

Re: Problem with enqueuing/dequeuing objects between threads
 
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



All times are GMT. The time now is 07:08 PM.

Powered by vBulletin®. Copyright ©2000 - 2014, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.