[No Audio]
In this video we will be explaining the
concept of barrier. A barrier enables
multiple threads to synchronize the beginning
of some computation, you may consider it like
an obstacle or a point in a code, which will
hault the execution of some threads until all
the threads have executed the code up to that
particular point. When all the thread reaches
the point, they will be allowed for further
execution of the remaining of their code.
This is typically used to synchronize the
beginning of some computation. Let us look at
an example to understand this. I will first
first bring the relevant modules into scope.
[No Audio]
Next, I will create a vector of threads.
[No Audio]
Now I will create a new barrier using the new function.
[No Audio]
Please note that since this variable needs to
be passed between multiple threads,
therefore, I have to wrap it inside the
Atomic Reference Counting smart pointer. This
will allow multiple threads to use it in a
correct way. Next, I will iterate 10 times,
and during each iteration, I will create a
thread which will do some computation. So I
will include a for loop for this purpose.
[No Audio]
Inside the loop, I will first create a clone
of the variable barrier. We can do this
because it is of type Arcs smart pointer
which has this function for allowing multiple
owners of the data in a thread safe way.
Next, I will create a thread during each iteration.
[No Audio]
Inside the thread, I will have some code to
reflect some sort of computation inside the
thread. So I will include a simple print statement.
[No Audio]
I want all the
threads that are being created inside the
loop to execute this print command, and then
wait until all the threads are done with
executing this command. In other words, I
want all the threads, all the threads code to
get synchronized at this particular point. To
do so I will call the wait function on the barrier.
[No Audio]
This will block the current thread until all
the threads have executed up to this
particular point. In other words, it will
block the threads until all the threads meet
at this point in code. After this line, I
will add one more print statement.
[No Audio]
This statement will only execute once all the
threads are done executing the respective
code up to the line on which on which each
specific thread is making a call to the
wait function. Outside the thread I will
collect all the threads in the thread vector.
[No Audio]
Finally, I will make sure that all the
threads should go to completion. So I will
call the join on all of them inside the loop.
[No Audio]
Now let us execute the code, and then we will
explain some of the output.
[No Audio]
You may note that
the first print line which was before the
wait on the barrier in each of the threads
are executed for the first five threads, and
afterwards the remaining of the code is being
executed. At the end of the terminal, you may
note that all the second print statements are
being executed. Let us explain the behavior
of the results now in the main. In the main,
the value of the barrier variable is set to 5.
This means that the first five threads
which will call this function will be blocked
until all of them are being synchronized.
This further means that when all the threads
have executed their respective code until the
point where they are calling the wait on
the barrier, then they will be unblocked.
Therefore the first five threads we are
creating are being blocked. When the first
five threads have displayed the message
before wait, which is an indication that
they are done with the code before the call
to the wait so they will be unblocked, the
Rust will allow the remaining of the code in
the respective threads to execute. However,
what happens is that, since we are creating 10
threads. So therefore when the remaining five
threads makes a call to the wait, they are
also blocked, and they will be released once
all of them are done with the code up to the
point where they are calling. We are making a
call to the wait function. In summary, the
first five threads will be blocked, and once
they are released the next five threads will
be blocked. If we change the value of the
barrier to date of 10, then in that case 10
threads will be blocked. Now what will happen
if I change the iteration of the loop to date
of 3 instead of 10? Let me change.
In this case, we are only creating three
threads. So all the three threads will be
blocked, when they reach to the line where
they are calling the function of wait. Since
the barrier has a value of five, which will
only resume once the execution of
five threads up to this particular point are
complete. Since we are not creating any more
threads, so therefore, all the threads will
keep on waiting and will never go to
completion. And this is because we are we are
only creating three threads and not five
threads. This example highlights a possible
issue with the barrier there is it may lead
to blocking of your program if misused or
used in a wrong way. But let us now cover a
nice use case where the barriers will be
useful, I will comment out the code and
we'll start fresh again.
[No Audio]
I will first bring the relevant modules into scope.
[No Audio]
Next I will create a vector of threads.
[No Audio]
Next, I will create a barrier variable wrapped
inside the Atomic Reference Counting smart pointer.
[No Audio]
We want to simulate a scenario where we have
some huge computation which is being spread
and divided among multiple threads. And we
would like to stop at some predetermined
points during the execution to check the
result. To achieve this functionality, we
will assume that we have three arrays
containing some values, we would like to
first add the first half of the arrays
array values and then add all the values
together and store it this will be followed
by the second half of the arrays. We will
create three threads where each thread will
be responsible for a one of the arrays. So I
will create a data vector which will contain
the arrays, and will have wrapped it inside
the Arc smart pointer.
[No Audio]
Please note that since this vector will be
passed to different threads, therefore, we
need to wrap it inside the Arc smart pointer.
Next I will create a variable which will
store the final summation of the values. Since
multiple arrays will be updating the values
of this variable, therefore to make sure that
the updation to this variable is being done
in a correct way. We will use the mutex, and
we'll wrap it inside the Arc smart pointer
[No Audio]
Please note that the vector of data is not
going to be updated and will be read only,
therefore we do not need to wrap it inside
the mutex however, this variable will be
updated inside multiple threads therefore, we
have wrapped inside the mutex. Next we will
iterate three times for creating three
threads, I will include a for loop for this purpose.
[No Audio]
During each iteration, we need to pass the
variables of barrier data and result to the
threads. So therefore, we will make long
copies of these variables.
[No Audio]
The clone copies will shadow the original
variables. Next I will create a thread.
[No Audio]
In each of the threads i, I will add the first
three values of the respective ith vector.
And next I will add the summation of the
first three values of the, of the vector or
array to the variable of result. To do so, I
will first obtain a lock on the variable of result.
[No Audio]
Next, I will add the variable x to
the already existing value of the result.
[No Audio]
I will next add a print statement to tell the
user that the thread has done its respective
part of the computation.
[No Audio]
We will call a wait
on the barrier next to make sure that all the
threads synchronize on this particular point.
Once this part is done for all the threads, we
will write similar code for part two of
all the threads. First we will compute
the summation of the remaining values.
[No Audio]
This will be followed by the
updation of the variable result.
[No Audio]
And finally, we will indicate that the
computation is being completed by the thread
using a print statement.
[No Audio]
Outside the thread we will collect the thread
in the thread vector.
[No Audio]
Moreover outside the loop, we will call we
will call join on all the threads to make
sure that all of them goes to completion.
[No Audio]
Finally, once all the threads are done we
will print the value of the result.
[No Audio]
Okay, let us cargo run this.
[No Audio]
You may note that
it only allows the second part of the
computation once all the threads are done
with the first part of the computations.
Okay, let me explain one final point before
we end this tutorial, and this is with regards
to the blocking behavior of the lock function
on mutex. You may recall that the lock
function on mutex will block the current
thread unless the locking variable remains in
scope. This means that if I create a variable
that locks a value, then as long as that
variable remains in scope, the lock remains
intact. For instance, I will comment out the
two lines in the start of the thread and also
the two lines after the wait function. Now
instead of doing in place change to the
value, I will create a variable x first,
which will acquire the lock.
[No Audio]
Next, I will update the value of the variable x
in the next line.
[No Audio]
After the wait I will update the value of the
variable x again.
[No Audio]
This will result in a
blocking state because whichever thread runs
for the first time, it will first acquire the
lock and then goes into the waiting state and
without releasing the lock. This will
essentially block all other threads to be
blocked whenever they try to acquire the lock
resulting in an overall blocking behavior.
The correct approach is to first acquire the
lock, and do not assign it to some variable
and then deref the value. This is non
blocking in nature by not assigning the lock
to some variable we are not bounding
ourselves to unlock the value. The mutex will
be automatically unlocked after the line of
code is executed. The lock in other words is
limited to a single line of code in this
case. The essential idea is that, if you want
to change the value in a single line and want
to immediately unlock after that line, then
you will first acquire the lock and then use
the deref to update the value. I call this
inplace lock and update. This does not come
with the extra headache of properly unlocking
the mutex, and is typically used in simple
scenarios just like the one we have. That is
it for this particular tutorial. See you
again and until then enjoy Rust programming.
[No Audio]