Growing up, my family had a Christmas Eve tradition of making wontons. It took about 10,000 of them to feed our household of eight, and each one had to be folded by hand. My mom calculated that this would have taken her three days to do alone, so she invented threading.

``import threading``

She recruited six workers (children) and gave us a stack of wrappers and a big bowl of filling and told us to get to work. She also set up a pan where we could put our freshly wrapped wontons.

``````worker_1 = threading.Thread(
target=fold_wontons, args=(pan, wrappers, filling))
target=fold_wontons, args=(pan, wrappers, filling))
…
worker_1.start()
worker_2.start()
…``````

Using this arrangement, she was able to put us all to work and focus on the deep frying.

As each of us finished wrapping a wonton, we added it to the pan, putting it at the end of the row. Because we were at different stages of fine motor skill development, our folding rates varied dramatically, but that was OK. We each just added our freshly folded wonton to the end of the row as we finished them.

``````def fold_wontons(pan, wrappers, filling):
while wrappers > 0 and filling > 0:
# It takes about 10 seconds for a 13 year old to fold a wonton.
time.sleep(10)
pan.put("wonton")``````

Then, as my mom cooked the wontons she would pull from the beginning of the row, the ones that had been on the pan the longest, and throw them in the oil.

``````wonton = pan.get()
fry(wonton)``````

In this way, my mom also invented the first in, first out (FIFO) queue.

``````import queue
pan = queue.Queue()``````

To reach even greater levels of efficiency, she moved to a batch processing approach. The wok she used was large and could cook many wontons at once, so she emptied the pan for each batch.

``````batch = []
while True:
try:
next_wonton = pan.get_nowait()
batch.append(next_wonton)
except queue.Empty:
break
fry(batch)``````

The `try-except` block in the `while True` loop let her repeatedly pull wontons from the pan until there weren't any left to take. Then she fried the whole batch together.

The snippets above aren't entirely complete. Here's a self-contained minimal example (also available in a GitLab repository.

``````import queue
import time

def fold_wontons(pan):
counter = 0
while True:
time.sleep(.1)
pan.put(f"wonton_{counter}")
counter += 1

pan = queue.Queue()
worker.start()

for i in range(10):
time.sleep(.4)
print(f"cooking batch {i}")
while True:
try:
next_wonton = pan.get_nowait()
print(next_wonton)
except queue.Empty:
break``````

In this code, wontons are created at a rate of 10 per second, but a batch takes 400 milliseconds to cook, so they end up getting cooked in batches of 4.

There are a whole lot of other useful and sneaky things you can do with threading. The official documentation for the threading package walks through this bag of tricks. My hope is that this example gives you a starting point from which to launch any new attack on threading concepts you need to build your latest project. An internet search for "python threading" will turn up a handful of other examples that will enrich your understanding too.

There are a couple more important tidbits that you'll need to take with you if you're planning to write some threading code of your own from scratch.

Tying up loose threads

When we create multiple threads, we incur little bit of responsibility for cleaning them up. There are a few ways to do this. The first is to just let them finish their job and terminate naturally. By default, the parent Python process won’t stop until all of the threads it created wrap up.

We can be explicit about this by calling `join()` on a thread. This tells the Python program to wait at this specific point until the thread is done doing its task. This is extra helpful if the rest of the program depends on the results of the thread task.

A third way of doing this is to declare the thread to be a daemon. That means it’ll go off and keep doing its thing, but as soon as the program that created it stops running it will automatically die. This is helpful for setting up a thread that continually grabs the latest packet or video frame and places it on a queue where it waits to be used by the main program. The convenience of a daemon is that you don't have to worry about closing it down. You can declare a threat to be a daemon when you create it with a `daemon=True` argument, as in the example above.