[No Audio]
In this video, we are going to learn about how
to handle data that is shared between
multiple threads. This is very important
topic in the context of concurrency. Since the
threads can execute concurrently, therefore,
we need to be careful while passing the data
between the threads to ensure that the reads,
and updation to the data is always done in
the right way. So let's get started. The
mechanism which is used for handling,
transferring, or using shared data between
multiple threads is called message passing.
The Rust achieves message passing using the
tool called a channel, which is included in
the standard library. The concept of channel
is fairly simple, and it may be understood
using the analogy of a water stream like a
river or a water stream. If you put something
like a boat or some other physical item on
the stream, it will travel down the stream to
the end of the waterway. A channel in
programming has two halves the transmitter
and the receiver. Going back to the river
analogy, the transmitter is the upstream
location where you would place the boat or
some other physical item that you want to
transmit to the other end, and the receiver is
the downstream location, where the boat will
end up and will be properly received. One
part of your code calls methods on the
transmitter passing, passing in the data you
want to send, and another part of your code is
listening to the receiver for arriving
messages. The channel is set to be closed if
either the transmitter or the receiver have
is dropped. From programming perspective, the
channels are like queue which follows the first
in first out order. That is the item or data
which is being inserted first will be the
first one which goes out of the queue, the
sender or transmitter will push items into
the queue and the receiver will read the
items from the queue in the same order in
which they are being inserted into the queue.
Remember that the order cannot be arbitrary
and will be strictly first in first out. Once
we learn the concept of channels, then we can
implement complicated stuff such as chat
system or program in which many threads
perform part of a calculation, and one thread
combines or aggregates the final result.
Let's see how channels are created in code.
First, we will bring the thread module and
the message passing module into scope.
[No Audio]
The mpsc stands for Multiple producer
single consumer. This means that the Rusts
implementation of channels allows to have
multiple sending ads for the data that
produces values, but only one receiving and
that consumes those values. The details of
this will become obvious during the video. In
this very basic example we will consider a
single producer, but we will aid multiple
producers later on. Moreover, we will create
a thread which will be communicated with a
main thread. In fact that thread will create
some data which will be passed to the main
thread. The channel function is used to
create a channel which returns a tuple.
Let us use this function.
[No Audio]
The function returns a tuple containing two
elements. The first returning element is the
sending end or the transmitting and the
second returning element is the receiver and
or the receiver.
The abbreviations tx and rx
are traditionally used for referring to the
transmitter and receiver respectively. The
variables are therefore named accordingly.
The last statement destructures the
tuple into the two variables, let us create a new thread.
[No Audio]
This thread will be acting as a producer so
therefore it will need to use the sender tx
variable the move ensures that it takes the
ownership of the variable tx for this purpose.
Please note that the spawn thread
needs to own the transmitter to be able to
send out messages through the channel. I will
next create a string variable containing some values.
[No Audio]
I will add a print statement also.
[No Audio]
Finally I will use the send method on the tx
variable and we'll pass to it the variable to
send to the main function.
[No Audio]
The Send method returns a result type. If the
receiver has already been dropped and there
is no where to send a value, the send
operation will return an error. In this
example we are calling the unwrap to panic in
case of an error. Please note that calling
the unwrap or an error generates a
panic in a real application we would handle
it properly and will not cause the program to
panic on errors. Let us now add some code
for receiving the value that has been sent
out by the thread. The receiver is a function
called receive, which is used to receive
values from the sender, let us use this function.
[No Audio]
This function blocks the thread which calls
it, and wait until the value is sent down the
channel. In this case it is erased since it
is being invoked by the main thread. So,
therefore, it will block the main thread
execution and will wait until a value
is sent down the channel. Once a value is
sent, the receive function will return it
inside an ok variant of the result enum.
This means that this function will also return a
result which either contains a value which is
being sent out or an error, in case
transmitter closes before even sending out a
useful value. The error is used to signal date
no more values will be coming. Finally, I
will print out the value that has been
received from the thread inside a print statement.
[No Audio]
Let us execute now to see the program in action.
[No Audio]
The program will always produce this
output this is because the main is being
blocked until the message from the thread has
been received. If I add another statement
after passing the message, then the program
output may be changed during different
executions. Let me add another print
statement to the thread.
[No Audio]
I will now execute a few times.
[No Audio]
You may note that the last print statement is executed
sometime before the main end sometime after.
Now, let us explain a few points with regards
to the ownership of the variables of rx and tx.
From the type of the rx and tx. We may
note that they are owned values and not
references. This means that they are
assuming the ownership of the respective
values of type sender and receiver. Now if I
add a line before creating the thread where I
moved the variable rx to another variable rx1.
You may note that the ownership has been
transferred and now rx is no more in scope.
In this case, we are unable to use the rx as
the value has been moved. Let me change back.
The next point with regards to the
ownership is that once a value is being sent
out, it is no more with a thread and
therefore its ownership will be transferred
to the thread where it has been received. To
see this I will add a print statement at the
end of the thread where I will try to access
the variable which has been
sent out to the main thread.
[No Audio]
You may note that the compiler is not happy
telling us that borrow of a moved value.
This is however, not the case with the
primitives as they are not moved but are
rather being copied. If I change the type of
the variable to date of an integer.
[No Audio]
You may note that, the compiler has no issues with this.
Let me comment out the last two print lines
in the thread for now and move to the next topic.
[No Audio]
Similar to the receive function, we
have a try receive function, this function is
however non blocking in nature when called it
will immediately return a result if the
message is currently not available then it
will return an error and if the message is
available then it will return an ok. This
method is very useful if the thread has other
work to do while waiting for the messages.
We could write a loop that calls a try_receive
every so often handles a
message if one is available and otherwise a
does some other work for a little while
until checking again. Let us see the use of
this function but first I will comment out
the lines in the main thread.
[No Audio]
First I will initialize a variable called
received_status from false.
[No Audio]
This variable will be set to true once the
try_receive function returns some
useful value. Next I will create a while loop
which will iterate as long as the variable
received_status is not true.
[No Audio]
Inside the loop I will match on try_receive function.
[No Audio]
If it returns an ok variant then I will
display the value which has been received and
is being wrapped inside the ok variant.
[No Audio]
I will also set the value of received_status to true.
[No Audio]
In case of error, I will print out
that I am doing some other useful stuff.
[No Audio]
This code will now and don't block the main thread.
If there are multiple threads, they
will get a chance as determined by the
scheduling algorithm of the operating system.
Let us cargo run this now.
[No Audio]
You may note that the man
did not blocked and keep on doing
some other stuff. Let us cover one more final
point before we end. What will happen if I
call are join before the code of try_receive
in the main thread.
Now, let's see this, I will assign the
thread to variable t and then I will call the
join on the variable t.
[No Audio]
In this case the main thread will be blocked
until and unless the thread t is being complete.
Afterwards the remaining of the
code in the main will get a chance to execute
let us cargo run this to see the output.
[No Audio]
This program will always produce this output, you
may note that the main thread is never given
a chance of doing something else. This is
because the man is blocked until the thread
goes to completion which ensures that we
always have a valid message to receive once
the thread is complete. Therefore we are
unable to do any other stuff. Since the
message is already available. This however
has a disadvantage of blocking the main
thread. With this we end this tutorial here
there are many details about message passing,
which we will be covering in the next
tutorial. See you again and until then
happy Rust programming
[No Audio]