Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Exploring Barriers

00:00 In the previous lesson, I showed you a higher-level synchronization primitive that combines locks and events called a condition. In this lesson, I’ll introduce you to barriers.

00:10 A barrier is like a lock combined with a semaphore. Threads wait at a barrier, and are blocked until enough arrive, reaching the threshold amount. Then they’re let through. Python’s Barrier object takes three arguments: the number of parties to wait upon, an optional function reference to call when the thread is released, and an optional timeout.

00:32 Let’s reconsider our teller situation and not allow anyone to get served until all the tellers are ready to work.

00:40 You are probably familiar with this pattern by now. First off, I create my synchronization object. This time it’s a barrier. I’m setting the party’s value to three, meaning three threads have to be at the barrier before they’re allowed through. The prepare _for_work() function simulates the teller prepping for their day.

00:57 Each teller is going to have a number, and to demonstrate the waiting, I’m going to have each teller’s prep time be equal to the seconds of their number. Like with an event or condition, this is where the thread waits.

01:10 In the case of the barrier, any threads at this point get held up until there are at least three joiners.

01:16 This time around, I’m creating three tellers, and the rest of the code is just the thread setup for each of them. Time to start the bank’s day. Running the script.

01:34 Let’s see what happened. Immediately, all three threads run the prepping function and then hit the sleep. And since each teller takes a different amount of time to sleep, it takes a while for them to pile up at the barrier.

01:46 Once the third teller is prepared, the barrier’s threshold has been met and all three tellers immediately announce that they’re ready.

01:54 Barriers are useful if you are using concurrency to break down a problem and want to make sure all the threads are done with their processing before the next step happens.

02:03 And that’s the last of the synchronization primitives covered in this course.

02:07 Now that you’ve seen them all, how do you know when you’re supposed to use them? First, do you have shared mutable data between threads? If you can avoid having mutable shared data, you can avoid synchronization primitives altogether.

02:21 The question you really need to ask yourself is whether any chunk of your code needs to be run atomically. This can be because of a shared resource or for other conditions as well.

02:31 Atomicity can also be important to ensure that several steps are performed together or not at all. You see this in databases with transactions. A series of operations are done, and if one of them fails, they all get rolled back.

02:45 Atomic blocks help you guarantee this kind of behavior.

02:49 Be aware though, it isn’t just your code that could be problematic. Don’t make any assumptions about other people’s code being thread safe. It most likely isn’t. If the documentation says nothing about it, assume it’s not thread safe.

03:03 And when I say other people’s code, that includes the Python standard library. In fact, something as common as print() isn’t thread safe. In theory, you could be in the process of printing out an object and have that object change midway through the print.

03:17 Logging, on the other hand, is thread safe to avoid exactly this kind of problem. You don’t want to have two log lines accidentally intermixed.

03:26 One lesson left. In it, I’ll summarize the course and point you at other topics that might be of interest to you.

Become a Member to join the conversation.