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