Async Techniques and Examples in Python Transcripts
Chapter: Thread safety
Lecture: Demo: Make the bank safe (fine-grained)
0:00 We saw that our now safe bank
0:01 uses a single global lock.
0:03 And if we look and we think a little bit about that
0:05 what that means is even if we have 50 threads
0:08 trying to do transfers between thousands of accounts
0:12 every single transfer is going to have to slow down
0:15 and go through a single thread
0:17 effectively a single processing pipeline
0:19 because we're taking a lock for the entire process.
0:23 And you may think well what we could do is we could
0:27 actually, instead of using the same lock
0:29 for every transfer, we could somehow come up with
0:31 a lock that has to do with the various accounts.
0:34 And if two accounts are being transferred by one thread
0:38 and two other accounts are being transferred
0:39 by a separate thread
0:41 those two could actually happen in parallel.
0:42 There's no data problem there.
0:45 The problem is if they're using the same account
0:48 you might think well we could gain a whole lot
0:50 of parallelism, a whole lot of performance
0:53 if we change from this coarse grain lock
0:55 to a very fine grain lock.
0:57 Now I want to say this right up front
0:59 this turns out to be probably a bad idea in Python
1:03 you'll see for performance reasons
1:05 in this particular example and it turns out to make
1:08 things a lot more complicated.
1:10 We'll make it work. I'll show you how to do it
1:11 but mostly the point of this
1:13 the takeaway is going to be, oh that added a whole lot
1:16 of complexity and it didn't really add any benefit
1:18 or maybe it even made it worse or will see.
1:21 Because of that, let's think about how much locking
1:25 and how much complexity we're willing to do
1:26 and what the trade-offs are.
1:27 Okay so this is almost a lesson in
1:30 don't go too far with this
1:31 because it might become worse and not better.
1:35 That said, let's set this up
1:37 and see what we're going to do.
1:39 So what we're going to do is have each account itself
1:41 have a lock. I'm going to take both locks of this and locks
1:45 take both of those locks at the same time
1:48 before we can do transfers between any two accounts.
1:51 Now that's easy.
1:52 Just go over here and just say self.lock = RLock().
1:58 And we see how to do this here.
2:01 So instead of doing with transfer lock
2:02 we're going to say from_account.lock like so
2:08 and to_account.lock.
2:11 And then we just indent this.
2:12 It looks like everything is golden.
2:15 It's going to be so wonderful.
2:16 But remember, this is a lesson in
2:19 what you need to be aware of.
2:20 So I'm going to do a little print statement here.
2:22 Because it's going to look like it actually goes slow
2:25 but it's not going slow
2:26 or there's something worse going on.
2:28 Let's just run it so you can see the whole path here.
2:31 So, what we're going to do is do a bunch of transfers
2:33 and we'll be done. Shouldn't take that long.
2:36 It's running. See we got some inconsistent balance.
2:39 We'll talk about why that is in just a sec.
2:46 It's just kind of sitting there.
2:47 It is computing? Is it taking longer? No!
2:53 If we go over here and we search for python
2:57 in the process, zero CPU. It's doing nothing.
3:00 It's just sitting there. What the heck is it doing?
3:03 Let's put some print statements.
3:04 We could also do some debug statements
3:06 but print statements will probably suffice here.
3:09 Taking first lock, ... Taking second lock.
3:16 And then we should see let's reverse that here.
3:23 Oh I'm not going to leave this code in.
3:24 But just so you see it
3:25 we're taking the lock, we're releasing the lock.
3:27 Things like that. All right again.
3:29 Oh, we're taking... Doing some release.
3:33 Taking first, taking second
3:34 taking first and then we're done.
3:36 We never got to taking the second lock.
3:40 And what's going on here?
3:41 So this is really hard to understand actually.
3:43 We have one, two, three, four, five, six accounts
3:46 and maybe six threads. If the accounts are separate
3:49 everything is fine. But what if we're you know
3:52 let's say thread 1 is transferring from account A to B.
3:57 Thread 2 is transferring from B to A.
4:00 They both get to line 64. They run that.
4:05 Thread A takes a lock on.
4:06 Sorry, thread 1 takes a lock on account A.
4:09 Thread 2 takes a lock on account B.
4:12 Now for B to continue, A has to release the lock
4:16 on the first account, right?
4:18 Sorry, thread 1 has to release the lock
4:20 on the first account and for thread 1 to continue
4:23 the lock on the other account has be to released.
4:25 But they're both stuck here waiting
4:28 for that second lock.
4:30 All right, they've sort of criss crossed over
4:31 on the two accounts and neither of them can make progress
4:34 so they're in what's called a deadlock.
4:36 So that's what's happened to our program.
4:37 It's in a deadlock.
4:38 You can see it's still trying to run here.
4:41 So let me try to get rid of it.
4:42 Kill these off.
4:44 So the problem is by doing this
4:46 we've actually created a deadlock.
4:48 Now the other thing we have to do
4:49 is this gets harder as well
4:51 our validate_bank.
4:52 Remember we're doing this safety check on the account.
4:55 We're just taking one lock
4:56 and then summing up all the accounts.
4:58 Well, now we can't do that.
5:00 We have to do it more like this.
5:02 Let's copy that.
5:04 We have to say account.lock.acquire for a in accounts.
5:11 And we should spell that right.
5:13 And then we should release it.
5:16 And of course we should put this in to try/finally
5:18 just to be safe but I'm just going to
5:19 put it here like this.
5:21 So we shouldn't see any of those errors anymore.
5:24 Any of the inconsistencies cause now it's really safe.
5:27 However, we're still deadlocked.
5:29 Taking first lock, taking first lock, you are done.
5:32 And these are where the threads are just finished.
5:35 They've all mangled themselves up
5:37 and they just stopped. So how do we fix this?
5:40 Well, that's what we're going to do next.
5:42 So we're going to come back and actually fix this
5:45 and see if this trick that we did
5:47 is really better than what we had before.