Problem with enqueuing/dequeuing objects between threads

Discussion in 'Perl Misc' started by Bryan Balfour, Jul 27, 2005.

  1. 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
    Bryan Balfour, Jul 27, 2005
    #1
    1. Advertising

  2. Bryan Balfour

    Paul Lalli Guest

    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
    Paul Lalli, Jul 27, 2005
    #2
    1. Advertising

  3. 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.
    Bryan Balfour, Jul 27, 2005
    #3
  4. Bryan Balfour

    Paul Lalli Guest

    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
    Paul Lalli, Jul 27, 2005
    #4
  5. 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, Jul 28, 2005
    #5
  6. 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
    Bryan Balfour, Jul 28, 2005
    #6
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Ian Pilcher
    Replies:
    7
    Views:
    388
    Mike Schilling
    Jan 19, 2006
  2. darren
    Replies:
    138
    Views:
    2,269
    Szabolcs Ferenczi
    Jun 3, 2008
  3. bsobaid
    Replies:
    1
    Views:
    862
    Vladyslav Lazarenko
    Apr 6, 2009
  4. Marcin Rodzik
    Replies:
    6
    Views:
    347
    Marcin Rodzik
    Aug 25, 2011
  5. Marcin Rodzik
    Replies:
    1
    Views:
    270
    markspace
    Aug 2, 2011
Loading...

Share This Page