Atomic Counters in Perl
Our primary mechanism for managing state in Perl is through shared variables. However, when dealing with concurrent programming, we need to be careful about how we access and modify shared state. In this example, we’ll look at using the threads::shared
module for atomic operations on shared counters accessed by multiple threads.
use strict;
use warnings;
use threads;
use threads::shared;
use Time::HiRes qw(usleep);
# We'll use a shared integer to represent our (always-positive) counter.
my $ops : shared = 0;
# We'll start 50 threads that each increment the counter exactly 1000 times.
my @threads;
for my $i (0..49) {
push @threads, threads->create(sub {
for my $c (0..999) {
# To atomically increment the counter we use lock.
{
lock($ops);
$ops++;
}
# Simulate some work
usleep(rand(100));
}
});
}
# Wait until all the threads are done.
$_->join for @threads;
# Here no threads are writing to 'ops', but using
# lock it's safe to atomically read a value even while
# other threads might be updating it.
{
lock($ops);
print "ops: $ops\n";
}
We expect to get exactly 50,000 operations. Had we used a non-atomic integer and incremented it without locking, we’d likely get a different number, changing between runs, because the threads would interfere with each other.
$ perl atomic_counters.pl
ops: 50000
In this Perl implementation:
We use the
threads::shared
module to create a shared variable$ops
that can be safely accessed by multiple threads.Instead of goroutines, we create 50 Perl threads using the
threads
module.We use the
lock
function to ensure atomic operations on the shared counter. This is equivalent to theatomic.Add
operation in Go.We simulate some work in each iteration using
usleep
with a random delay.We join all threads at the end to wait for their completion, similar to the
WaitGroup
in Go.Finally, we print the value of
$ops
, which should be 50,000.
Note that Perl’s threading model is different from Go’s goroutines. Perl threads are more heavyweight and the performance characteristics may differ. Additionally, Perl’s threads are not as widely used for concurrent programming as goroutines are in Go, but they serve a similar purpose in this example.
Next, we’ll look at other synchronization primitives for managing state in concurrent Perl programs.