Threading
Let us explore the Threading module. Firstly let’s look at an example of how much time a code takes to run when we do not use threading and when we do use threading.
import time # starting a counter start = time.perf_counter() def sleeper_function(sleep_length): print("Going to sleep now:") time.sleep(sleep_length) print("Finished sleeping") # without threads sleeper_function(1) sleeper_function(1) # ending a counter stop = time.perf_counter() print(f"Total time taken: {round(stop-start, 3)} secs") # rounding it to 3 digits
The time taken to run is:
Let’s consider the time taken for the same code but when we use threads.
# with threads time_1 = threading.Thread(target=sleeper_function, args=[1]) time_2 = threading.Thread(target=sleeper_function, args=[1]) time_1.start() time_2.start()
Let’s break down the above code, particularly the code that uses threads. Consider the entire code when we use threads.
import threading import time # starting a counter start = time.perf_counter() def sleeper_function(sleep_length): print("Going to sleep now:") time.sleep(sleep_length) print("Finished sleeping") # with threads time_1 = threading.Thread(target=sleeper_function, args=[1]) time_2 = threading.Thread(target=sleeper_function, args=[1]) # starting our threads time_1.start() time_2.start() # adding to main thread time_1.join() time_2.join() # ending a counter stop = time.perf_counter() print(f"Total time taken: {round(stop-start, 3)} secs") # rounding it to 3 digits
The time module offers a function called “perf_counter()”, which we can use to start a counter. So we create 2 variables to monitor the start and end of the counter, and the time elapsed will be the difference between these 2 variables. The value returned is float and a large decimal number, thus we round it to three digits after the decimal.
As we discussed in the previous blog, the sleep function halts the flow of a program for a specified number of seconds. We then created two objects of the class Thread (from the threading module) and gave them two arguments. Firstly, the name of the function we want our thread to use, and secondly the arguments we want to use for that particular function. To start a thread, we simply need to call the start() function.
We then called the join function for each of the individual threads. The necessity of adding the join function is that each of the threads gets added to the main thread. If that did not make much sense let’s run this code without the join() function and see the output.
As we can see before the threads could finish their execution the main thread (or script) has come to an end. To make sure that the main thread before completion, waits for all other threads to come to an end, we need to use the join() function for each of those threads.
Functions of the Thread Module
Let’s consider a few functions from the Thread module. The first one is the activeCount() function, which as the name indicates results in the active number of threads within a script (aka Python file).
import threading, time def sleeper_function(sleep_length): print("Going to sleep now:") time.sleep(sleep_length) print("Finished sleeping") time_1 = threading.Thread(target=sleeper_function, args=[1]) time_2 = threading.Thread(target=sleeper_function, args=[1]) time_1.start() time_2.start() print(f"There are {threading.activeCount()} active threads") time_1.join() time_2.join() print("Script has ended")
When we run this, we get:
NOTE: We see there are 3 active threads, the first two are the threads we created and the third one is the main thread.
The second function is enumerate() which lists all of the active threads in our script.
import threading, time def sleeper_function(sleep_length): print("Going to sleep now:") time.sleep(sleep_length) print("Finished sleeping") time_1 = threading.Thread(target=sleeper_function, args=[1]) time_2 = threading.Thread(target=sleeper_function, args=[1]) time_1.start() time_2.start() print("The active threads area:") print(threading.enumerate()) time_1.join() time_2.join() print("Script has ended")
We get the output as:
The third function is current_thread(), which returns which object of the Thread is currently active.
import threading, time def sleeper_function(sleep_length): print("Going to sleep now:") time.sleep(sleep_length) print(f"The currently active thread is: {threading.current_thread()}") print("Finished sleeping") time_1 = threading.Thread(target=sleeper_function, args=[1]) time_2 = threading.Thread(target=sleeper_function, args=[1]) time_1.start() time_2.start() print(f"The currently active thread is: {threading.current_thread()}") time_1.join() time_2.join() print("Script has ended")
Consider the output of this program, notice how the main thread comes before the two threads we created. This is because the print statement of the main thread is before the join() functions of our threads, and so the main thread continues to be executed till it encounters the join() functions. The individual threads are only visible within the target functions.
What have we learned?
- How to initiate a counter using the time module?
- How do we create a thread using the “threading” module?
- What are the parameters of the Thread object?
- What is the main thread?
- What is the use of the join() function?
- What are some of the functions we can use from the “threading” module?