Merry Christmas!
I hope this calendar has been of some use to you all. In any case:
Merry Christmas!
I hope this calendar has been of some use to you all. In any case:
Merry Christmas!
Lots of big words in the title. In simpler terms, it means running a program in
the background and interacting with it as input and output becomes available.
The tool in Raku for doing this work is called
Proc::Async. If you’ve ever dealt
with the pain of trying to safely communicate with an external process, writing
to its input and reading from its output and error streams and hated it, I think
you’ll like what Raku has built-in.
Raku actually provides two different locking classes. A
Lock object provides a very standard
locking mechanism. When .lock and .unlock are used or .protect is called,
you get a section of code that pauses until the lock frees up, runs while
holding the lock, and then frees the lock so other code that might be waiting on
the lock can run.
However, the Lock class works in such a way that blocks the current thread. As
I’ve pointed out earlier in this advent calendar, the purpose of threads is to
do stuff, so blocking them from running is preventing them from fulfilling their
purpose. Luckily, there is a solution.
What’s more asynchronous than socket communication? When two programs need to talk to
each other, often from different computers on different networks in different
parts of the world, you can connect using a socket. Whether an HTTP server or
some custom protocol, you can implement both sides of that communication using
IO::Socket::Async.
Let’s consider a simple calculator service. It listens for connections over TCP.
When a connection is established, it takes lines of input over the connection
and parses each line as a simple mathematic calculation like 2 + 2 or 6 * 7.
Iteration is slow. If you have N things to process in a loop, your loop will take N iterations to process. Slow. Sometimes that’s the only way, though, to solve a problem.
For example, let’s consider the case where we have a JSON log and we want a command to read each line, parse the JSON for that log, and summarize it showing the time stamp and message:
use JSON::Fast;
my $log-file = 'myapp.log'.IO;
for $log-file.lines -> $line {
my %data = from-json($line);
say "%data<timestamp> %data<message>";
}
If you have multiple cores on your system (and who doesn’t in 2019?), you can actually speed this up a little bit with a small change:
The goal of today’s article is to consider when you want to run your tasks simultaneously and how to do that. I am not going to give any rules for this because what works one time may not work the next. Instead, I will focus on sharing some guidelines that I have learned from personal experience.
Whenever you use concurrency, you want to hold on to the related
Promise objects. They are almost
always the best way to rejoin your tasks, to cause the main thread to await
completion of your concurrent tasks, etc.
As I have said several times before in this calendar, it is always best to avoid sharing state between running threads. Again, however, here is yet another way to share state, when you need to do it.
A few days ago, we considered monitors as a mechanism for creating a thread-safe object. Let’s consider the following monitor:
class BankBalanceMonitor {
has UInt $.balance = 1000;
has Lock $!lock .= new;
method deposit(UInt:D $amount) {
$!lock.protect: { $!balance += $amount };
}
method withdraw(UInt:D $amount) {
$!lock.protect: { $!balance -= $amount };
}
}
The day after
that
we considered the compare-and-swap operation, a.k.a. cas, and how to use it
with any scalar variable in Raku. By using cas, we can actually create thread
safe objects without using locks at all.
In Raku, a Supply is one of the primary
tools for sending messages between threads. From the way a Supply is
structured, it is obvious that is provides a means for one ore more tasks to
send events to multiple recipient tasks. What is less obvious, however, is that
a Supply imposes a cost on the sender.
Consider this program:
my $counter = Supplier.new;
start react whenever $counter.Supply {
say "A pre-whenever $_";
sleep rand;
say "A post-whenever $_";
}
start react whenever $counter.Supply {
say "B pre-whenever $_";
sleep rand;
say "B post-whenever $_";
}
start for 1...* {
say "pre-emit $_";
$counter.emit($_);
say "post-emit $_";
}
sleep 10;
Here we have three tasks running, each in a separate thread. We let the main
program quit after 10 seconds. The first threads two receive messages from the
$counter.Supply. The third thread feeds a sequence of integers to this supply.
You might be tempted to think that the final task will race through the delivery
of events, but if so, you’d be wrong.
In Raku we have a couple basic ways of getting at the events emitted from a
Supply, which begs the question, what’s
the difference between each? I want to answer that question by creating a
react block
with a couple intervals and then emulate the same basic functionality using
tap.
Let’s start with our base react block:
sub seconds { state $base = now; now - $base }
react {
say "REACT 1: {seconds}";
whenever Supply.interval(1) {
say "INTERVAL 1-$_: {seconds}";
done if $_ > 3;
}
say "REACT 2: {seconds}";
whenever Supply.interval(0.5) {
say "INTERVAL 2-$_: {seconds}";
}
say "REACT 3: {seconds}";
}
The seconds routine is just a helper to give us time in seconds from the start
of the block to work from. The output from this block will typically be similar
to this:
A semaphore is a system of sending messages using flags. Oh wait, that’s what a
semaphore is outside of computing. Among computers, a semaphore is like a kind
of lock that locks after being acquired N times. This is useful for situations
where you have a resource of N items, want to quickly distribute them when you
know they are available, and then immediately block until a resource has been
released. Raku provides a built-in
Semaphore class for this reason: