[No Audio]
In this section we will explore in detail the
Rust concurrency feature. First of all, let
us explain what exactly do we mean by the
term concurrency. Concurrency refers to when
section of code, when sections of code in our
application runs parallel to other sections
of code. This means if we have some code
block or segment residing inside the same
file, the concurrency will allow these
segments to run concurrently or in other
words in parallel. The term concurrency is
also often referred to as multi threading. In
most current operating systems, an executed
programs code is run in a process, and the
operating system manages multiple processes
at once. within your program, you can also
have independent parts that run
simultaneously. This feature that runs these
independent parts is called threads. In
summary, concurrency means executions of
different parts of our code simultaneously,
and the mechanism through which it is made
possible is known as threads. All the
programs we have seen so far in this course,
run the respective code sequentially section
after section. By using threads, we can run
sections of our code in parallel. This means
that each subsequent section do not need to
wait for the section above it to complete
fully before it can be executed. There are
many essential advantages of this, consider a
case where one part of our program may wait
for some input, thereby unnecessarily
blocking the remaining of the code. We can
create threads where the input output may be
assigned to one thread, and the remaining of
the computation to the other. This way, the
program will not unnecessarily block the
remaining of the code from execution while
waiting for the input to be received. With
this background, let us start coding the
threads, and things will become more easy to
comprehend and understand.
To use the threads
we need to include the thread from the
standard library.
[No Audio]
Let us add a few print statements before
including a thread in our main program.
[No Audio]
To create a thread, we need to use the
function of spawn which takes a
closure as an input.
[No Audio]
Let us add some code to the body of the
thread inside the closure.
I will add a few print statements.
[No Audio]
Next I will add a couple of print statements
to the body of the main function outside the thread.
[No Audio]
Okay, now let us explain some important
points. The print statements at the start
of the program will always be printed to the
terminal. This is because we are creating
threads after this line, and therefore the
concurrency in the program has not yet
started. Therefore, these print statements
will be printed in sequential order. Next we
have spawned a thread. Each program by default
has one thread which is the main thread. At
this point now, we have created another
thread in addition to the main thread after
this point, the remaining of the code either
belongs to the main thread of or this newly
created thread, threads can execute in
parallel therefore, the print statement
inside the thread and the two print
statements in the remaining of the main
function will execute in parallel.
The specific scheduling order in which they will
be executed is not deterministic, and is being
handled by the underlying operating system.
Therefore, we cannot guarantee any order in
which they will be executed. Let us execute
now, and then we will explain some important points.
[No Audio]
Let me execute a few more times.
[No Audio]
You may note that the first three lines are
always being printed, and it is because the
concurrency starts after the first three
lines in the main function. Next, there is no
unique order in which the print statements
inside the thread and the main are being
executed. Each time we execute, we obtained
different result. Sometimes the thread is
executed partially, and then switched to the
main, and at other times the main is is
executed first, and then a chance is given to
the thread. And at some other times multiple
switches may take place between the main, and
the created thread. As explained earlier,
this is because the thread scheduling are
being handled by the operating system and
there is no deterministic order in which they
will be executed. We may also note that
in all the executions, the print statements
at the end of the main are always been
executed, while some of the print statements
inside the thread may not execute. This is
because the main thread will always complete
execution before the termination of the
program, while the remaining of the threads
may or may not. Once the main thread of the
Rust program completes, all spawned threads
are shut down whether or not they have
finished their respective execution. For this
reason, some of the print statements are not
executed during our runs of the program. To
give more chance to your thread to go to
completion before the end of the main thread,
you may use a function called thread sleep.
The call to thread::sleep
forces our thread to stop its execution for a
short duration, allowing a different thread
to run. To use this function I will first include
the relevant module from the standard library.
[No Audio]
Next, I will use it in the main after
creating the thread.
[No Audio]
This will now block the
main thread for a for one millisecond thereby
giving a slightly more chance to the thread
to execute. Let us cargo run to see the result.
[No Audio]
We can, we can increase the sleep
time to give more chance to the thread, the
threads will probably take turns but that
isn't guaranteed. It depends on how your
operating system schedules the threads. If
you run this code and only see output from
the main thread, or don't see any overlap,
try increasing the sleep time. Now let us
learn how to ensure that the spawn thread
runs to the completion before the end of the
main program, and do not end prematurely. This
can be done by making use of the joint
function or the join handle. The join handle
is the returning value of a thread spawn
function, which we can store in a variable
let me define a variable and assign to it the
result of the thread spawn. You may note
that the variable type is date of join handle.
Now we can call the join on the
variable of handle, so let me call it.
[No Audio]
Calling this function for a specific thread will halt
or block the execution until the thread is
complete. This means now that if I execute
this program then the thread will complete
first and after that the remaining statements
in the main will get a chance to execute. So
let us cargo run this to confirm.
[No Audio]
Now what will happen if I move the call to join at
the end of the main function, let me move it.
[No Audio]
In this case now the thread and the main will
keep on getting chances but the main thread
waits because of the call t.join, and
does not end until the spawn thread is
finished. Let us execute to confirm this.
[No Audio]
Please note that the join function will
return a result, it will result an Ok
variant when the thread does not panic and it
will return an error if the thread panic due
to some reason.
Okay before we end there are
a couple of important points to note.
Firstly, there are two terminologies of
concurrency and parallelism, which are very
related but different within the context of
computer science. The concurrency is about
multiple tasks which starts run and complete
in overlapping time periods in no specific
order. parallelism is about multiple tasks or
sub tests of the same task that literally run
at the same time on a hardware with multiple
computing resources like multicores
processors. From programming perspective, we
are interested in concurrency and from the
hardware perspective, that is the underlying
architecture involving physical design and
components. We are interested in parallelism.
Programming community, however, uses the
terms interchangeably as it is understood
that they are referring to the concurrency
and not to parallelism, since
parallelism is beyond their scope. Secondly,
threads are often associated with problems
such as race conditions, where threads are
accessing data or resource in an inconsistent
order and deadlocks where two threads are
waiting for each other to finish using the
resource, preventing both the threads from
continuing and finally bugs, which can only
happen in certain situations and are hard to
reproduce and fix reliably. Rust attempts to
mitigate the negative effects of using
threads as it will become evident to us in
the upcoming tutorials. We end this tutorial
here see you again with more concepts and
until next tutorial enjoy Rust programming.
[No Audio]