driedupsharpie
driedupsharpie

Reputation: 143

How To Prevent Perl Thread Printouts from Intercepting?

Suppose I made three perl threads that each do this:

print hi I am thread $threadnum!;

print this is a test!;

you would expect an output of: hi I am thread 1

this is a test!

hi I am thread 2

this is a test!

hi I am thread 3

this is a test!

but instead, what happens about half of the time is: hi I am thread 1

this is a test!

hi I am thread2

hi I am thread3

this is a test!

this is a test!

is there any was to ensure that they output in the correct order WITHOUT condensing it all into one line?

Upvotes: 1

Views: 172

Answers (2)

Sobrique
Sobrique

Reputation: 53488

Fundamentally here, you're misunderstanding what threads do. They're designed to operate in parallel and asynchronously.

That means different threads hit different bits of the program at different times, and potentially run on different processors.

One of the drawbacks of this is- as you have found - you cannot guarantee ordering or atomicity of operations. That's true of compound operations too - you can't actually guarantee that even a print statement is an atomic operation - you can end up with split lines.

You should always assume that any operation isn't atomic, unless you know for sure otherwise, and lock accordingly. Most of the time you will get away with it, but you can find yourself tripping over some truly horrific and hard to find bugs, because in a small percentage of cases, your non-atomic operations are interfering with each other. Even things like ++ may not be. (This isn't a problem on variables local to your thread though, just any time you're interacting with a shared resource, like a file, STDOUT, shared variable, etc.)

This is a very common problem with parallel programming though, and so there are a number of solutions:

Use lock and a shared variable:

##outside threads:
use threads::shared; 
my $lock : shared;

And inside the threads:

{ 
   lock $lock;
   ### do atomic operation
}

When the lock leaves scope, it'll be released. A thread will 'block' waiting to obtain that lock, so for that one bit, you are no longer running parallel.

Use a Thread::Semaphore

Much like a lock - you have the Thread::Semaphore module, that ... in your case works the same. But it's built around limited (but more than 1) resources. I wouldn't use this in your scenario, but it can be useful if you're trying to e.g. limit concurrent disk IO and concurrent processor usage - set a semaphore:

use Thread::Semaphore;
my $limit = Thread::Semaphore -> new ( 8 );

And inside the threads:

$limit -> down(); 
 #do protected bit
$limit -> up(); 

You can of course, set that 8 to 1, but you don't gain that much over lock. (Just the ability to up() to remove it, rather than letting it go out of scope).

Use a 'IO handler' thread with Thread::Queue

(In forking, you might use a pipe here).

use Thread::Queue;
my $output = Thread::Queue -> new (); 

sub print_output_thread {
    while ( $output -> dequeue ) { 
        print; 
    }
}

threads -> create ( \&output_thread ); 

And in your threads, rather than print you would use:

$output -> enqueue ( "Print this message \n" ); 

This thread serialises your output, and will ensure each message is atomic - but note that if you do two enqueue operations, they might be interleaved again, for exactly the same reason.

So you would need to;

$output -> enqueue ( "Print this message\n", "And this message too\n" ); 

(you can also lock your queue as in the first example). That's again, perhaps a bit overkill for your example, but it can be useful if you're trying to collate results into a particular ordering.

Upvotes: 1

user149341
user149341

Reputation:

First of all: don't use perl interpreter threads.

That being said, to prevent those lines from being printed separately, your options are either:

  • Acquire a semaphore while printing these two lines to prevent two threads from entering that critical section at the same time.
  • Print a single line of text with an embedded newline, e.g:

    print "hi I am thread $threadnum\nthis is a test!\n";
    

Upvotes: 1

Related Questions