Threads in Python

Photo by SkitterPhoto on Pexels

What’s a thread ?

Conceptually, threading is a way of telling your code to do multiple things at the same time, importantly, there is a need for order and intercommunication, there is also a specific syntax and a few caveats; an individual thread can then be understood as an independent ( although it can talk to other threads ) and concurrent ( other things are happening ) process.

In Practice

There are many instances where threading can help you solve a problem, in my specific case it was a pesky GUI that needed to call other functions and was hanging ( freezing ) my program, threads solved the issue. Rather than recreate that complex problem here, I opted for analogies and simple scripts that will hopefully help you get the gist and then you can use threading for your specific projects.

The Initial Problem

It’s not uncommon to want 2 things to happen at the same time, let’s start with an example that doesn’t quite work:

import time
import logging
def sleep_5_seconds():
logging.info(' Will sleep 5 seconds')
time.sleep(5)
logging.info(' Done sleeping 5 seconds')
def sleep_10_seconds():
logging.info(' Will sleep 10 seconds')
time.sleep(10)
logging.info(' Done sleeping 10 seconds')
def main():
logging.basicConfig(level = logging.INFO)
sleep_5_seconds()
sleep_10_seconds()
main()OUTPUT:
INFO:root: Will sleep 5 seconds
After 5 seconds:
INFO:root: Done sleeping 5 seconds
INFO:root: Will sleep 10 seconds
After 10 seconds:
INFO:root: Done sleeping 10 seconds
import threading
import time
import logging
def sleep_5_seconds():
logging.info(' Will sleep 5 seconds')
time.sleep(5)
logging.info(' Done sleeping 5 seconds')
def sleep_10_seconds():
logging.info(' Will sleep 10 seconds')
time.sleep(10)
logging.info(' Done sleeping 10 seconds')
def main():
logging.basicConfig(level = logging.INFO)
THREAD1 = threading.Thread(target = sleep_5_seconds)
THREAD2 = threading.Thread(target = sleep_10_seconds)
THREAD1.start()
THREAD2.start()
main()
OUTPUT:
INFO:root: Will sleep 5 seconds
INFO:root: Will sleep 10 seconds
After 5 seconds
INFO:root: Done sleeping 5 seconds
After another 5 seconds
INFO:root: Done sleeping 10 seconds
ℹ️ Pythons threading documentation (for the whole feature set) :https://docs.python.org/3.8/library/threading.html?highlight=threading#module-threading

Basic communication with the Main Thread.

You might think that we were running 2 threads in the previous example, but in reality we had 3 ( Surprise ! ) , you can verify this by running print(threading.enumerate()) after starting the threads, it should give you something like this:

[<_MainThread(MainThread, started 140735828833152)>, <Thread(Thread-1, started 123145406754816)>, <Thread(Thread-2, started 123145412009984)>]
import threading
import time
import logging
def sleep_5_seconds():
logging.info(' Will sleep 5 seconds')
time.sleep(5)
logging.info(' Done sleeping 5 seconds')
def sleep_10_seconds():
logging.info(' Will sleep 10 seconds')
time.sleep(10)
logging.info(' Done sleeping 10 seconds')
def sleep_2_seconds():
logging.info(' Will sleep 2 seconds')
time.sleep(2)
logging.info(' Done sleeping 2 seconds')
def mainTHREAD():
logging.basicConfig(level = logging.INFO)
THREAD1 = threading.Thread(target = sleep_5_seconds)
THREAD2 = threading.Thread(target = sleep_10_seconds)
THREAD3 = threading.Thread(target = sleep_2_seconds)
THREAD1.start()
THREAD2.start()
THREAD2.join() #<- mainTHREAD will wait for THREAD2.
THREAD3.start()
mainTHREAD()
OUTPUT:
INFO:root: Will sleep 5 seconds
INFO:root: Will sleep 10 seconds
After 5 seconds:
INFO:root: Done sleeping 5 seconds
After another 5 seconds:
INFO:root: Done sleeping 10 seconds
INFO:root: Will sleep 2 seconds
After 2 more seconds:
INFO:root: Done sleeping 2 seconds

Thread Options:

Some miscellaneous thread options you should be aware, think of them as upgrades.

⚠️ These are the current options of the threading.Thread class, class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)let's explore a couple...

Thread names:

We can name our threads:

import threading
import time
def sleep_5_seconds():
time.sleep(5)
def main():
THREAD1 = threading.Thread(target = sleep_5_seconds,
name = 'Pinky')
THREAD1.start()
print(threading.enumerate())
main()Output:[<_MainThread(MainThread, started 140735743599488)>, <Thread(Pinky, started 123145322811392)>]
import threading
import time
import logging
def sleep_5_seconds():
time.sleep(5)
def main():
logging.basicConfig(level = logging.INFO)
THREAD1 = threading.Thread(target = sleep_5_seconds)
logging.info('Thread1 is alive: ' + str(THREAD1.isAlive()))
THREAD1.start()
logging.info('Thread1 is alive: ' + str(THREAD1.isAlive()))
THREAD1.join() # Wait till Thread1 ends...
logging.info('Thread1 is alive: ' + str(THREAD1.isAlive()))
main()
OUTPUT:
INFO:root:Thread1 is alive: False
INFO:root:Thread1 is alive: True
After 5 seconds:INFO:root:Thread1 is alive: False
THREAD1 = threading.Thread(target = sleep_5_seconds,name ='Pinky')logging.info(THREAD1.name + '. is alive: ' + str(THREAD1.isAlive()))OUTPUT:INFO:root:Pinky. is alive: False

Thread Arguments

So far our threads have been simple over explicit functions for demonstration purposes, but threads can also have arguments and have a thread factory that produces them:

import threading
import time
import logging
def sleepy_function(seconds): #<- Our thread factory.
logging.info(' Will sleep '+str(seconds)+' seconds')
time.sleep(seconds)
logging.info(' Done sleeping '+str(seconds)+' seconds')
def main():
logging.basicConfig(level = logging.INFO)
THREAD1 = threading.Thread(target = sleepy_function, args=(5,))
THREAD2 = threading.Thread(target = sleepy_function, args=(10,))
THREAD1.start()
THREAD2.start()
main()
OUTPUT:
INFO:root: Will sleep 5 seconds
INFO:root: Will sleep 10 seconds
After 5 Seconds:
INFO:root: Done sleeping 5 seconds
After another 5 Seconds:
INFO:root: Done sleeping 10 seconds

Daemon Threads

One last thing to discuss is the Daemon flag in the Thread Object daemon=None ,what this flag does when set to true: daemon=True is treat this thread as a disposable, non critical one, that is, if you exit your program and this thread is still running it will close automatically, but with no guarantee it has accomplished its task, so these threads are used for non critical and recurrent tasks. If on the other hand you do not specify it’s a daemon thread and you exit while the thread is still running, your thread will raise and exception (ie, things didn't happen as you wanted). I recommend you start with non daemonic threads.

Conclusion:

Threads allow you to have control over what happens when during the course of your program or script, the simple but profound change from doing things sequentially to concurrently opens a set of new opportunities and challenges to you as a software developer, and I hope this serves you as a gentle introduction to the subject…

Where to go next ? This has been only an introduction to threads, I might do a second part to cover more advanced topics, in the mean time I recommend the following:https://realpython.com/intro-to-python-threading/

AI, Software Developer, Designer : www.k3no.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store