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 worker()
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
task. Then worker()
can listen to messages from both main()
and
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
signal, then worker()
reacts immediately by sending the latest result to
main()
, and 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 montecarlopi()
:
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 montecarlopi()
.
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 worker()
and
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
signals from main()
(lines 8-11) and from montecarlopi()
(lines
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 montecarlopi()
take ownership of the sender,
but also proc()
.
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.