[No Audio]
In the last tutorial we talked about message
passing through channels, which provides one
way of transferring data between threads. In
this tutorial, we will be looking at an
alternate way of transferring data through
shared states. The message passing is a one
way data flow in which a sender thread passes
a message to a receiving thread, the
ownership is transferred from the sending
thread to that of the receiving thread. With
shared state concurrency on the other hand,
we have some piece of data residing inside
the memory that multiple threads can read and
write to at the same time. The Rust gives
you the flexibility to pick between these two
different approaches. Let us look at the
details of transferring data through shared
states. The first thing we will talk about
are the mutexes which is an abbreviation
for mutual exclusion. Within the context of a,
within the context of concurrency the term
mutual exclusion means that you that you have
some piece of data, and only one thread can
access that piece of data at any given time.
To achieve this mutexes uses a locking
system. When a thread wants to access to a
piece of data behind the mutex, it will
signal that it wants access to that data, and
acquired a lock on the mutex. The lock is, the
lock is a type of a data structure that keeps
track of which thread has exclusive access to
a piece of data. Once a thread has required a
lock on a particular piece of data, no other
thread can access that data. Once the thread
is done with that piece of data, it can
unlock the data and allow other threads to
have access to that particular data.
Typically mutexes have a reputation for being
hard to manage because you have to remember
two rules. Firstly, you have to acquire a
lock before you have access to the data. And
secondly, you have to release that lock once
you are done with the data, so that other
threads can have access. And now this is
sometimes a bit hard to always remember to
unlock whenever you lock a mutex. This
sometimes lead people away from using it.
Fortunately, Rust type system and ownership
rules guarantees that you can't get locking
and unlocking wrong. Let's see an example and
things will start to make sense. We will
start with the basic working of mutex. I will
first bring the relevant modules into scope.
[No Audio]
I will create a new, a new mutex that holds the integer 5.
[No Audio]
Just like many other types to create a new
mutex, we will call the associated function
new, and then pass in the data we want to
store let's access the data in our mutex.
First I will create an inner scope, and then I
will create a variable called num, and it's
going to be mutable.
[No Audio]
I will next set its
value equal to m.lock. This means that we
are acquiring a lock and then we will call
unwrap. As explained, the lock method is used
to acquire the lock, and this will actually
block the current thread until it is able to
acquire the lock. This means that if there
are multiple threads who wants to access to
the data by calling the lock, then all of them
will be blocked until the lock is released.
At any given time only one thread will
be allowed to acquire the lock, and the
remaining will be blocked. The call to lock
returns a result i because if there is
already a thread that has acquired a lock to
that particular data, and that particular
thread panics, then calling lock will fail.
And in this case, we call unwrap, which is
saying that if lock fails, then panic.
The type system is making sure that we call lock,
because m is not a simple integer, but it is
rather a mutex that holds an integer so we
can't directly manipulate that integer until
we call the lock. After calling lock and
unwrap, we get a mutex guard smart pointer
whose deref trait points to the inner data of
the mutex, which in this case is the integer 5.
Also a MutexGuards implements the drop trait
such that when MutexGuard goes out of
scope, then it releases the lock to the data.
This means releasing the lock will be done
automatically for you, so you don't have to
remember all the time to release the lock. We
can now go ahead and manipulate the
underlying data by using the dereference operator.
[No Audio]
Finally at the end of the main, let's
go ahead and print m and cargo run again.
[No Audio]
You may note that, the variable m now stores
the value, and now let us cover another
example. I will comment out the code in the
inner scope.
[No Audio]
I will create a variable num
which will acquire a lock on the mutex.
[No Audio]
I will next create another variable which will
acquire another lock on the mutex.
[No Audio]
I will next update the inner values using the
respective variables. This code seems
correct. Let us cargo run this.
Seems like the program has halted.
Let me stop it and see the code again.
I believe you would have
spotted the problem in this case. As pointed
out before, when a lock is being acquired
inside a thread than any other thread or
within the same thread, any additional call
to the lock will block the thread until the
lock is released. In this case, the first
call to the lock will acquire the lock and
the thread will not be blocked. However, when
the lock is again being tried for the second
time, then it blocks the thread until the
first block is being released. Since that can
only happen when the locking variable goes
out of scope, which can only happen at the
end of the main thread which is never
encountered therefore the thread is being
blocked. This blocking major of mutexes leads
to an issue that is referred to as deadlocks.
A dead lock is a situation in which, threads
sharing the same resource are effectively
preventing each other from accessing the
resource. There is anything in halting or
blocking of both the correct way will be to
first update the value using the variable
num. Following this I will drop the variable
num which will automatically unlock the mutex.
[No Audio]
Next I will obtain the lock using the
variable num1. This will be followed by
the updation of the variable num1. Finally
we will drop the variable num1 also. This
manual dropping may not be always
required within the context of threads since
when the threads and its variables are also
being dropped. We end this tutorial here as
there are many details to cover about the
mutexes in the next tutorial, we will be
covering how the mutexes can be used within the
context of multiple threads.
Until then happy Rust programming.
[No Audio]