Async Techniques and Examples in Python Transcripts
Chapter: Thread safety
Lecture: Demo: Make the bank safe (global)

Login or purchase this course to watch this video and the rest of the course contents.
0:00 We've seen safe bank is massively not safe and our job is to come in here and make it safe. So, how are we going to do that?
0:10 Well, I'm going to use a different construct from the threading library. So we're going to import what you might think is Lock.
0:18 Alright, Lock that's an obvious thing I would like to get however we do not want to use Lock. There's actually two different types of Locks in Python.
0:27 Lock, which is a very low-level thing and RLock, or Reentrant Lock. The problem with Lock is if you call it from a function
0:37 and that function somehow through some series of function calls ends up calling into that Lock again which is more likely than you would actually think
0:47 it's going to deadlock. Right, so the lock cannot even be entered by a thread that owns that lock, and that's pretty limiting
0:54 and it's hard to reason about. RLock means the thread itself can enter the lock many times as long as it exits as many times as it does
1:02 but no other threads can get there and that's really the behavior we want so we're going to get RLock here.
1:07 Now, what we're going to do is we're going to start out by doing a very coarse grain thing that just says
1:13 let's fix this one problem down here and do transfer. It's this little bit that puts our program into a temporarily invalid state
1:22 and then puts it back, so our goal with our Lock is to tell the other threads stay out, I'm about to put things into a temporarily
1:30 invalid state, and in this coarse grain way what we're going to do is we're going to create one lock to say no other thread can do a transfer
1:38 while any given thread is doing the transfer. So, first of all, let's run it just to see how long this takes. It's takes .71 seconds. Okay, great.
1:47 That's the unsafe version. Now, let's go and create our lock. Let's call it transfer_lock. Okay, we we've created the lock, that's pretty awesome.
2:00 Now, we want to go down here and I'll show you the not so good way and I'll show you the best way. So first of all, we're going to come down here
2:08 and do it, I'll show you not good. Go to the lower lock and we're going to say acquire(). You can see we can set a time out and things like that.
2:16 we're just going to say we're going to block this thread until we get a chance to get in here and then we're going to go down here and say release().
2:23 This will actually fix the problem. This will make everything good. But I said this behavior this pattern I'm using is not so good.
2:32 It's going to work fine for what we have because I know there's no possibility of any of these operations failing, there's no chance of returning
2:39 early or anything like that, but in reality we probably need to do this. Try and then finally, just in case there's some
2:49 kind of exception in here we want to make sure that we release the lock. What happens if there's an exception and we catch
2:55 it higher up but we don't do this? Program deadlock, we never, ever get into this function again ever. That's a pretty high consequence
3:04 so you probably want to write this code instead of the prior code, I'll leave that one like that. So we could do the not so good kind
3:13 but let's do something a little better. We can just say now with transfer_lock: and do the things we want to do.
3:20 Like this, and of course this should be committed out cause we don't actually want to do it twice. So that, try, do this thing, finally release it
3:28 well that's exactly what this with block does and you don't have to think about it you don't have to be really cautious about it.
3:33 So, you want to do this with block. Now, our goal is to run this program and have it run the very first time when it says everything was great
3:41 there were no errors, let's go. Man, that is so fantastic. Maybe it was just luck, alright, could be one of these
3:49 weird heisenbugs, granted we did see a lot of errors so I'm pretty confident we fixed it. Well, let's just run it again, just to be sure.
4:00 Nope, it definitely seems to be working so we fixed it, we made sure that any time our program is going to move into one of these invalid states
4:07 we're going to tell all the other threads hold on, I'm about to mess up some stuff you shouldn't look at this, I'm going to do that
4:12 and then I'll put it back let you all run as soon as it's done. Notice it did take .15 seconds, .15 seconds longer than it did here, 1.14.
4:23 However, would you rather be slightly faster and wrong or slightly slower and right? We did take away some of the parallelism
4:31 some of the concurrency that our threads had to work with but it was worth it, it was totally, totally worth it.

Talk Python's Mastodon Michael Kennedy's Mastodon