Python programming exercises
Concurrency means a program is doing mutliple things at the same time. In computing this is achieved through the use of multiple processors or time-slicing, sharing a single processor between tasks by rapidly switching between them.
Mutliple processors can also be used for parallel-processing where the same task, but with different data, is run on each processor to reduce the time taken to compute results.
In Python there are various ways of creating programs with concurrent tasks. The most popular is threading, and this is used in other programming languages. In a threaded program the code is shared between all the threads, there is no need to write separate units of code for each one, but there will be several parts of the program being executed at the same time.
Here is a simple example where a thread is created to run a countdown. Note that both the main program and the thread created using the threading module use sleep() but doing so only pauses the relevant part of the program.
from threading import Thread
from time import sleep
def countdown_activity():
for i in range(10, -1, -1):
print(f" COUNTDOWN {i}")
sleep(1.5)
print(" Thread ends.")
# Change to daemon=True and observe the different behaviour.
mythread = Thread(target=countdown_activity, daemon=False)
print("About to start thread...")
mythread.start()
for i in range(5):
print(f"Main program counter {i}")
sleep(0.4)
print("Main ends.")
Creating many threads is easy. The program will end when all non-daemon threads have finished. The main program is a non-daemon thread. However, it can be very hard to rationalise what a program with many threads is doing at any point in time, and extremely difficult to debug if it doesn't behave as expected. Therefore we need to think carefully about our design.
A event-loop is an ideal design pattern for launching threads to complete user requests.
Concurrency can add significant complexity to the run-time environment. However, used appropriately it can simplify the design and implementation of software and produce better performance.
Something we can see already is that a daemon-thread can end suddenly, perhaps during file or database access.
Even when a thread runs to completion it doesn't usually return any data to the main program. Threads aren't a "non-blocking" alternative to function.
If we want to pass data between threads we need to use appropriate "thread-safe" communication such as a Queue.
This is a more advanced example that uses threads to enable audio playing, database access and a GUI to run simultaneously. The main program runs the tkinter GUI and coordinates communication with the two other threads.
Must be in the same directory (folder) as music.db
cd code
python app.py
There are some basic tests in code/tests. Pytest should find these automatically if PYTHON_PATH is correct. The easiest way to ensure this is to run using python -m
> cd code
> python -m pytest --cov .
https://docs.python.org/3/library/queue.html
http://tkdocs.com/tutorial/morewidgets.html#listbox
https://docs.python.org/3/library/tkinter.html#threading-model
-
Chapters 29 and 30 of Hunt Advanced
-
Coroutines https://en.wikipedia.org/wiki/Coroutine
-
Designing with threads https://www.oreilly.com/library/view/the-art-of/9780596802424/ch04.html