The code examples of this blog post are available in the Git repository tasks-and-messages.
In part 1 of this series we started implementing our Pi calculation using the Monte Carlo method. We ended with code that works, but that still doesn’t return a value after exactly 10 seconds. In this part we’ll finish the implementation.
The problem with the previous implementation was that the
function had to wait for
montecarlopi() to return, before it could
react to the message from
main(). The solution to this should now be
obvious: Let’s put the
montecarlopi() calculation in a separate
worker() can listen to messages from both
montecarlopi() at the same time.
Here’s the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
And here’s the output from running the program. As you can see from
lines 12-15 it’s now working as intended. First
main() sends the
worker() reacts immediately by sending the latest result to
montecarlopi() is left to finish its calculation (but
the result is discarded).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Now let’s go through the code and see what we had to change to make it
work. First let’s look at
1 2 3 4 5 6 7 8 9 10 11 12 13
Now that it’s in its own task it has to communicate with the
worker() function and send it the result of the calculation. This is
as easy as passing in a
Sender when calling it. The only interesting
bit here is that we use
send_opt() to send the result to the
worker() instead of
send(). This is because
send() aborts the
program when it can’t send the message (i.e. the receiver is gone). We
need to handle this case as
worker() may now return before
montecarlopi() is done.
So far so good. Now we need to have a look at
worker(). It needs to
change to wire it up correctly with the new
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
First we need a new channel to communicate between
montecarlopi(). Then we start the first calculation in a new task.
And after that we enter the endless loop. In it we check for both
main() (lines 8-11) and from
12-20). If there’s a message from
main() it means we’re done and we
exit the loop. If there’s a message from
montecarlopi() it means
that the calculation is done. We then update our best guess of Pi and
start another calculation.
The concept used here in
worker() isn’t that complex. What was the
most difficult for me to get right was the setup of the channel. You
can see here that we need to pass a copy of sender. This is due to the
fact that not only does
take ownership of the sender,
This is designed so that Rust can safely move the
proc() and all the
data associated with it to a different task. And we of course have to
have the channel defined outside of the loop so that all tasks send
their data back to the same task.
And this is it for this post! In the next part we’ll have a look at how we can simplify this design. I don’t know about you, but it took me quite a while to get this design right. I can’t imagine using it like this in production code.