Lock Objects
In this lesson, you’ll learn about lock objects and how you can use a lock as a context manager to prevent more than one thread from accessing a part of your program at the same time. If you download the sample code, you can get your own copy of 10-lock.py
:
To learn more, you can also check out the documentation.
00:00 In the previous lesson, you created a Python program that demonstrated a race condition. Now, you’re going to learn about locks and how we can prevent that undesired outcome.
00:12
Go ahead and import the threading
module and create a lock
, and that comes from threading.Lock()
. Now, locks are in either one of two states: they’re either locked or they’re unlocked. We can print(lock)
and drop into a terminal and we’ll see that it is unlocked
.
00:34
When you first instantiate a Lock
, it will be in its unlocked state.
00:40
The way that locks can be locked is through a method called .acquire()
, so we can say lock.acquire()
00:49
and then if we print lock
again, we’ll see that it is now locked
. So, what actually does the acquiring of a Lock
is the thread.
00:59
In this case, our main thread—since we haven’t created any threads explicitly—the main thread is what has acquired the Lock
. Any code below this—for example, this print statement—is locked by the main thread.
01:16
No other thread can access anything below this .acquire()
call.
01:22
So, the way that we can unlock a Lock
, unlock a section of our code, is through the .release()
method. .release()
will unlock the particular area of code.
01:37
If we print lock
again and we come into the terminal, we’ll see that it is locked
here, because we just called .acquire()
, and then we released it, and now it’s unlocked
again.
01:51
That’s the basic information about the threading.Lock
object. We lock it by using .acquire()
, and we unlock it by using .release()
.
02:00
We can also use a Lock
as a context manager. And when we do that, we don’t have to manually or explicitly call .acquire()
or .release()
.
02:11
We can use a with
block to let that handle it for us. This makes solving the race condition in our previous lesson very simple to fix. I’m just going to open up the old file and bring it into this 10-lock.py
.
02:30
And recall that this is our shared data, this self.balance
. We want the thread to lock right here. This is the part of our program that is manipulating the shared data.
02:44 We only want one thread to access this at the same time.
02:50
So. We can use our Lock
context manager by simply saying with
—well, we have to define a Lock
first. So let’s actually, right below our .balance
in our .__init__()
method, we’ll say self.lock
is equal to a threading.Lock
object, and we will need to import the threading
package up here.
03:14
Now we have this .lock
, so the thread is going to be able to lock this particular part of the code. with self.lock
, and then everything in here we just want to bring into that with
block.
03:29
I’m going to save this and clear the screen. And remember that when we didn’t have a lock, our ending .balance
ended up to be -50
because of the miscommunication of what happened in the .update()
method. Now that we have locked it down, only one thread—the deposit thread—is going to be able to finish completely before the withdrawal thread can execute any of this code in here.
03:57
Let’s go and execute our program. We’ll see it’s starting with the .balance
of a 100
and finishing with our desired .balance
of 0
, and we’ll see the deposit thread updated and then the withdrawal thread updated, and then the deposit thread finished, withdrawal thread finished.
04:16 Everything’s in order and nothing was executed here by more than one thread at the same time.
04:24
And we can run this as many times as we want, and in theory, it should always give us the ending .balance
of 0
.
Become a Member to join the conversation.
malbert137 on June 1, 2020
Technically, there is no guarantee that the the “withdraw” thread acquires the lock before the “deposit” thread, but this should be very rare in practice. In real life, the ordering of the deposit and withdraw would be determined by the account holders (e.g., perhaps the account is held by two people, and one is making a deposit at one location while the other is trying to make a withdraw at another location), so the code wouldn’t be sequencing those actions. In any case, there is no check for overdraw.