Multithreaded Python Tutorial with the “Threadworms” Demo

Photo from Brad Montgomery)

Here’s a common metaphor that is used: Say you have two robot ticket sellers. Their tasks are simple:

  1. Ask the customer which seat they want.
  2. Check a list to see if the seat is available.
  3. Get the ticket for that seat.
  4. Cross that seat off the list.

A customer asks Robot A for seat 42. Robot A checks that the seat is available from the list and finds that it is, so it grabs the ticket. But before Robot A can cross the seat off the list, Robot B is asked by a different customer for seat 42. Robot B checks the list and sees that the seat is still available, so it tries to grab the ticket for the seat. But Robot B can’t find the ticket for seat 42. THIS DOES NOT COMPUTE, and Robot B’s electronic brain explodes. Robot A then crosses seat 42 off of the list.

The above problem happens because although the two robots (or rather, two threads) are executing independently, they are both reading and modifying a shared list (or rather, a variable). Your programs can get very hard-to-fix bugs which are also difficult to even reproduce, since Python’s thread execution switching is nondeterministic, that is, done differently each time the program is run. We aren’t used to having the data in variables “magically” change from one line to the next just because a different thread was executed in between them.

When the execution switches from one thread to another, this is known as a context switch.

There is also the problem of deadlocks, which is commonly explained using the metaphor of the Dining Philosophers. Five philosophers are sitting around a circular table eating spaghetti but require two forks to do so. There is one fork between each philosopher (for a total of five forks). The method the philosophers use to eat is this:

  1. Philosophize for a while.
  2. Pick up the fork on your left.
  3. Wait until the fork on your right is available.
  4. Pick up the fork on your right.
  5. Eat.
  6. Put the forks down.
  7. Go back to step 1.

Aside from the fact that they’ll be sharing forks with their neighbors (eww), it seems like this method will work. But sooner or later everyone at the table will end up with the fork on their left in their hand and waiting for the fork on their right. But because everyone is holding on to the fork their neighbor is waiting for and won’t put it down until they’ve eaten, the philosophers are in a deadlock state. They will be holding forks in their left hand but never getting a fork in their right hand, so they never eat and never put down the fork in their left hand. The philosophers all starve to death (except for Voltaire who is actually a robot. Without spaghetti, his electronic brain explodes.)

There is also a similar situation called a livelock. This is when no work gets done because the threads are too generous at making a resource available. The best metaphor of this is when two people are walking towards each other down a hall. They step to the side to let the other person walk past, but end up blocking each other. So they both step back to the other side, but end up blocking each other again. They continue doing this until they starve/electronic-brain-explode.

There are a few other problems that can come up with multithreaded programming such as starvation (no seriously, that’s what it is called) and generally fall under the label of “Concurrency” in computer science. But we will only treat a simplified case.

Locks

One way to prevent bugs with multithreaded programming is by using locks. Before a thread reads or modifies a shared variable, it attempts to “acquire” a lock. If it can acquire the lock, the thread goes on to read or modify the variable. If the thread cannot acquire the lock, it waits until the lock becomes available.

When the thread is done with the shared variable, it will “release” the lock so that some other thread waiting for the lock can acquire it.

Page 2 of 10 | Previous page | Next page