Search code examples
multithreadingperlscopelexical-scope

Sharing a thread variable without making it global (Perl)


I'm trying to write a simple script that uses threads and shares a variable, but I don't want to make this variable global to the whole script. Below is a simplified example.

use strict;
use warnings;
use threads;
use threads::shared;

my $val:shared;

# Create threads
for my $i (1 .. 5) {
    threads->create(\&do_something, $i);
}

# Wait for all threads to complete
map { $_->join(); } threads->list();

# $val is global to the script so this line will work!
print "VAL IS: $val\n";

sub do_something {
    my $i = shift;
    print "Doing something with thread $i!\n";

    {
        lock $val;
        $val = "SOMETHING IS $i";
        print "$val\n\n";
    }
}

Output:

Doing something with thread 1! SOMETHING IS 1

Doing something with thread 2! SOMETHING IS 2

Doing something with thread 3! SOMETHING IS 3

Doing something with thread 4! SOMETHING IS 4

Doing something with thread 5! SOMETHING IS 5

VAL IS: SOMETHING IS 5


How can I get this effect without making $val accessible to the whole script? In other words, how can I make it so attempting to print VAL IS: $val will fail, but the variable will still be successfully shared by the threads?


I can't define it like this:

# Create threads
for my $i (1 .. 5) {
    my $val:shared;
    threads->create(\&do_something, $i);
}

Or I will get:

Global symbol "$val" requires explicit package

What is the right way to lexically scope a shared variable?


Solution

  • Pass a reference to it as an argument.

    sub do_something {
       my ($id, $lock_ref) = @_;
       print("$id: Started\n");
       {
          lock $$lock_ref;
          print("$id: Exclusive\n");
          sleep(1);
       }
       print("$id: done.\n");
    }
    
    {
       my $lock :shared;
       for my $id (1..5) {
          async { do_something($id, \$lock); };
       }
    }
    

    Or scope it so only the worker subs can see it.

    {
       my $lock :shared;
    
       sub do_something {
          my ($id) = @_;
          print("$id: Started\n");
          {
             lock $lock;
             print("$id: Exclusive\n");
             sleep(1);
          }
          print("$id: done.\n");
       }
    }
    
    for my $id (1..5) {
       async { do_something($id); };
    }