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.

More Locks

In this lesson, you’ll continue exploring locks and learn about deadlock and how to avoid it. A lock is also called a mutex in other languages. If you download the sample code, you can get your own copy of 11-more_locks.py:

Download

Sample Code (.zip)

12.9 KB

To learn more, you can also check out the documentation on threading.RLock and threading.current_thread as well as the Wikipedia page for deadlock.

00:00 In the previous lesson, you created a Lock and used it as a context manager within your account program to prevent race conditions. In this lesson, you will explore a little bit more about locks and get to experience something called deadlock. Go ahead and import the threading module and create a lock from threading.Lock().

00:25 You already know that locks start out in their unlocked state and they have two methods—or, more than two, but two main methods we’re going to learn about are Lock.acquire(),

00:38 which locks a particular part of code, and Lock.release(), which unlocks a particular part of code. Anything in between here is protected

00:52 and only one thread, the owner of the lock, is allowed to access any code in between the .acquire() and .release() statement. Now, the context manager, in other words the with block that you wrote, makes it so we don’t have to explicitly do this ourselves.

01:08 But if you aren’t using a context manager and you have many threads, it’s very easy for deadlock to happen. Now, what is a deadlock? It’s when you have a thread, try to lock a Lock that is already locked.

01:22 So, it’s a thread trying to lock a Lock that is already locked—in other words, two calls to .acquire() at the same time. When we go ahead and execute this program,

01:36 it will put us in a frozen state. Like, I can’t get back to my shell prompt. I’m in deadlock. And that is awful—no one wants their programs to freeze like this.

01:48 One way you can prevent that is how you did previously, which is use Lock as a context manager and let the threading package handle the .acquire() and .release() for you.

01:59 Another way is to just manually count, “Oh, I have made two acquires, so that means I need to release before this .acquire(), and then that should be fine,” which it is.

02:11 But it’s really bad practice to manually, like, rely on a human to keep track of all these different locks and their states when you have numerous threads working.

02:23 Another option is to use a reentrant lock, or an RLock. By simply adding an R before Lock, you create a reentrant lock.

02:32 I’m going to add an R here as well. And what that does is it allows you to make multiple calls to .acquire() without causing deadlock.

02:45 I’m going to do two calls to .acquire(), and previously, this would have caused deadlock, but notice how I have the shell prompt back—it’s all good.

02:55 Now what’s going on here? Let’s print(rlock)

03:00 and we’ll see a little slightly different output here. So, it is locked. The owner is this number, so this is the thread that owns rlock.

03:11 And we have a count of 2, so it appears that there have been two calls to .acquire(), represented by this count number. Now, if we do rlock.release(), this affects our count number, and it brings it back down to 1.

03:29 So we can use this information to tell, like, the state of the locks that the thread owns. And another fun little fact here is this number may seem a little mysterious, but really it’s just the identifier of the thread that owns the Lock.

03:45 And threading offers a nice little function called .current_thread(), and if we print .current_thread(), it will give us information about the thread that is being executed.

03:57 This one is _MainThread and it’s started, and here’s its identifier, which we can verify is the owner of the rlock.

04:08 Now you have more knowledge of locks and you know what an RLock is, and you know about using locks as a context manager and how they take care of this acquiring and releasing for you.

04:22 So now you have a little more tools in your arsenal and knowledge about locking and mutexes.

Anonymous on Nov. 21, 2019

Thank you for this very useful course. You explained the reentrant lock syntax but you didn’t explain where it is useful, what situation would requires a reentrant lock?

Thanks

Lee RP Team on Nov. 21, 2019

I’m glad you found this course useful. Good question about the use case of reentrant locks. RLocks are used in the same way as regular Locks, except that a thread can acquire more than one lock if it’s an RLock. When you use RLocks you are less likely to run into deadlock situations where a thread tries to acquire the lock more than once. A user named Erik on StackOverflow also gave another use case:

suppose you acquire a granular lock (ie: on a file) and do something fairly expensive (ie: decrypting it). an rlock prevents deadlocks from forming in a complex series of dependencies that, also, may try to acquire the same lock.

Hope this helps, Lee

Become a Member to join the conversation.