Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set your subtitle preferences in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please refer to our video player troubleshooting guide for assistance.

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:

Download

Sample Code (.zip)

12.9 KB

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 withwell, 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.

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.

Become a Member to join the conversation.