简体   繁体   中英

multithreading with MQ

I'm having problem using MQSeries Perl module in multi-threading environment. Here what I have tried:

  • create two handle in different thread with $mqMgr = MQSeries::QueueManager->new() . I thought this would give me two different connection to MQ, but instead I got return code 2219 on the second call to MQOPEN() , which probably means I got the same underling connection to mq from two separate call to new() method.
  • declare only one $mqMgr as global shared variable. But I can't assign reference to an MQSeries::QueueManager object to $mqMgr . The reason is "Type of arg 1 to threads::shared::share must be one of [$@%] (not subroutine entry)"
  • declare only one $mqMgr as global variable. Got same 2219 code.
  • Tried to pass MQCNO_HANDLE_SHARE_NO_BLOCK into MQSeries::QueueManager->new() , so that a single connection can be shared across thread. But I can not find a way to pass it in.

My question is, with Perl module MQSeries

  • How/can I get separate connection to MQ queue manager from different thread?
  • How/can I share a connection to MQ queue manager across different thread?

I have looked around but with little luck, Any info would be appreciated.

related question:


Update 1: add a example that two local MQSeries::QueueManager object in two thread cause MQ error code 2219.

use threads;
use Thread::Queue;
use MQSeries;
use MQSeries::QueueManager;
use MQSeries::Queue;

# globals
our $jobQ = Thread::Queue->new();
our $resultQ = Thread::Queue->new();

# ----------------------------------------------------------------------------
# sub routines
# ----------------------------------------------------------------------------

sub worker {
    # fetch work from $jobQ and put result to $resultQ
    # ...
}

sub monitor {
    # fetch result from $resultQ and put it onto another MQ queue
    my $mqQMgr = MQSeries::QueueManager->new( ... );

    # different queue from the one in main
    # this would cause error with MQ code 2219
    my $mqQ = MQSeries::Queue->new( ... );

    while (defined(my $result = $resultQ->dequeue())) {
        # create an mq message and put it into $mqQ
        my $mqMsg = MQSeries::Message->new();
        $mqQ->put($mqMsg);
    }   
}

# main
unless (caller()) {
    # create connection to MQ
    my $mqQMgr = MQSeries::QueueManager->new( ... );
    my $mqQ = MQSeries::Queue->new( ... );

    # create worker and monitor thread
    my @workers;
    for (1 .. $nThreads) {
        push(@workers, threads->create('worker'));
    }
    my $monitor = threads->create('monitor');

    while (True) {
        my $mqMsg = MQSeries::Message->new ();

        my $retCode = $mqQ->get(
            Message => $mqMsg,
            GetMsgOptions => $someOption,
            Wait => $sometime
        );

        die("error") if ($retCode == 0);
        next if ($retCode == -1); # no message

        # not we have some job to do
        $jobQ->enqueue($mqMsg->Data);
    }
}

There is a very real danger when trying to multithread with modules that the module is not thread safe. There's a bunch of things that can just break messily because of the way threading works - you clone the current process state, and that includes things like file handles, sockets, etc.

But if you try and use them in an asynchronous/threaded way, they'll act really weird because the operations aren't (necessarily) atomic.

So whilst I can't answer your question directly, because I have no experience of the particular module:

Unless you know otherwise, assume you can't share between threads. It might be thread safe, it might not. If it isn't, it might still look ok, until one day you get a horrifically difficult to find bug as a result of a race condition in concurrent conditions.

A shared scalar/list is explicitly described in threads::shared as basically safe (and even then, you can still have problems with non-atomicity if you're not locking).

I would suggest therefore that what you need to do is either:

  • have a 'comms' thread, that does all the work related to the module, and make the other threads use IPC to talk to it. Thread::Queue can work nicely for this.

  • treat each thread as entirely separate for purposes of the module. That includes loading it (with require and import - not use because that acts earlier) and instantiating. (You might get away with 'loading' the module before threads start, but instantiating does things like creating descriptors, sockets etc.)

  • lock stuff when there's any danger of interruption of an atomic operation.

Much of the above also applies to fork parallelism too - but not in quite the same way, as fork makes "sharing" stuff considerably harder, so you're less likely to trip over it.

Edit:

Looking at the code you've posted, and crossreferencing against the MQSeries source:

  • There is a BEGIN block, that sets up some stuff with the MQSeries at the point at which you use it.

Whilst I can't say for sure that this is your problem, it makes me very wary - because bear in mind that when it does that, it sets up some stuff - and then when your threads start, they inherit non-shared copies of "whatever it did" during that "BEGIN" block.

So in light of what I suggested earlier on - I would recommend you try (because I can't say for sure, as I don't have a reference implementation):

require MQSeries; 
MQSeries->import;

Put this in your code - in lieu of use - after thread start. Eg after you do the creates and within the thread subroutine.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM