Async Techniques and Examples in Python Transcripts
Chapter: Thread safety
Lecture: Demo: Breaking a deadlock
0:00 So here you see, we have our various print statements
0:03 and we found that we go taking first lock
0:05 taking first lock, done.
0:06 This is deadlocking. How do we fix it?
0:08 Well, the fundamental problem here is
0:10 that different threads could say
0:13 take first a lock on account A then B
0:15 or maybe they go B then A
0:17 and it's that order that is the problem.
0:19 If we could guarantee all threads first took a lock on A
0:22 and then they took a lock on account B
0:24 regardless of whether that was the from or to account
0:27 well then we could actually be great.
0:29 We could just take those locks
0:30 and we wouldn't have this deadlock problem.
0:33 So, how do we fix that?
0:35 Well, we can just decide some order for the accounts.
0:38 If accounts had ID's we could figure out
0:40 which one is lower and use that one first.
0:43 Our account just has a balance and a lock
0:45 but we can just ask for the memory address basically
0:48 the Python ID of the pointer and that'll be good enough.
0:52 So we can just say lock1, lock2.
0:56 This is going to be a little clumsy
0:57 and I can't think of a great way
0:58 in Python to write it better, so let's just do this.
1:01 We'll say from_account.lock
1:05 to_account.lock, and we're going to do a test.
1:09 If the id can get the id basically gives you back a number
1:12 based on the pointer from_account
1:14 is less than id of to_account.
1:17 We'll say else, and return these in a different order.
1:25 Then we just take this and say, you know what?
1:26 We're using lock1 and we're going to use lock2.
1:31 Now let's try it and see if it deadlocks.
1:35 The deadlocked one is still running again.
1:37 Kill it off there.
1:41 It's slow because it has all these print statements.
1:44 But it's not deadlocked, is it?
1:46 Boom, it finished. Let's get rid of these.
1:51 Alright, now we can run it without
1:52 any of those print statements, just let it run pure.
1:57 Whoo! So, do you see the huge performance benefit
2:00 we got from this?
2:01 We went and we said, look, we have six threads
2:03 I have 12 cores, we have a bunch of different accounts.
2:06 Let's actually let all of this happen in parallel
2:09 and we're going to do way better.
2:11 Our program is going to be so much faster
2:13 because now, as long as it's not
2:16 involving any of the two same accounts
2:18 we can actually do concurrent transfers.
2:21 Right, now we don't have that many here.
2:24 If we added more accounts, you might think this gets better.
2:27 But it turns out, this part gets faster
2:31 but this part down here, those parts
2:35 actually get equally slower.
2:38 So, adding more accounts, which would
2:40 add more concurrency actually just still
2:42 kind of makes it more computational.
2:44 And it turns out that it's just worse to remember
2:47 what did we get before.
2:50 Let me pin that, run that safe bank.
2:53 This is the global one. We got 1.1 seconds.
2:58 This new fancy high-performance version
3:00 gave us 1.3 seconds and a bunch of deadlocks.
3:03 So, I'm not saying you should never
3:04 use fine-grained locks, I'm just saying
3:06 you should think about whether it makes sense
3:09 to use fine-grained locks or coarse-grained locks.
3:12 And really the most important takeaway from this
3:15 whole section has to do with this.
3:19 If you're taking two locks from two different things
3:22 you have to make sure you're always taking them
3:24 in the same order, or there's a good chance
3:25 you're going to deadlock your application.
3:28 You don't want to do that, trust me.
3:29 That is a really, really bad place to be.
3:31 It's no fun to figure out what happened
3:33 or why this is happening.
3:35 Taking two locks, always take them the same order
3:37 you don't have to use my little ordering technique
3:40 figure out some way to order it
3:41 but this works for this particular situation.