Python Multithreading
Time is the most important factor in programming. The time required to execute and process a code should be practical. Thus, we need to know how to reduce time boundaries in our code.
Python Multithreading provides a simple yet powerful way to do this. But, before going to multithreading directing, we should know what is a thread first.
Thread
You might know that CPUs have multiple cores on them, but why do we need multiple cores? Well, each core on the CPU can only run one process at a time. So, in order to run multiple processes, you need more cores. What is a process, then?
In programming, a process can be defined as an instantanization of the program that is being executed in the runtime. A process can have multiple parts in it like the data that is being used, the program itself, and the current state of the process.
Finally, we come down to the definition of a Thread:
- A thread is a unit of a process with independent instructions that can be manipulated to get executed at a specific time,
- In other words, it’s the smallest part of a process with different and unrelated instructions that can be scheduled for execution.
- A thread has a beginning time, execution sequence, and a result or output.
- Threads have their own register sets and local variables. Each thread can use all of the global variables and the program code.
Now that we know what is a Thread. Let us see what is Multithreading now.
Multithreading
Multithreading is to run multiple threads in parallel.
We will use multithreading as by executing multiple threads in parallel, you can significantly increase the performance of a program.
Context Switching
When a thread is running, if it gets interrupted by the system in any way, a new thread is loaded and executed. This is called Context Switching and it is so frequent that our threads look like they are running in parallel.
Python provides the threading module to implement multithreading and create multiple threads. This module gives us an object-oriented programming approach for this task. Let’s import this module and create two new threads.
Example
You can consider this as the basic syntax of creating a new thread without any arguments in Python. When we print two variables in which the threads are stored, we can see that both threads are now automatically serialized and initialized by the threading module.
Now, threading.Thread() object has some arguments, let’s see what are those and understand them:
Syntax:
threading.Thread(group, target, name, args = (), kwargs = {}, daemon)
- group: This argument is reserved for future purposes when we implement the ThreadGroup (class in threading module) class and for now should be set equal to None.
- target: It is an object (usually a function) that contains the main program that will be executed by the current thread.
- name: We can give names to our threads, by default, threads are given names in the format “Thread – N” just like we have seen in the example above.
- args: This is a tuple that contains our arguments for our target object.
- kwargs: This is a dictionary with keyword arguments for the target object.
- daemon: After Python version 3.3, this new argument has been added to the object. This argument sets whether our thread is a daemon or not.
In the multitasking world of computing, a daemon can be described as a background process that cannot be directly controlled by the user.
Consider this Python multithreading example, where we compute the factorial of two numbers.
Example
Oh! Did you notice that?
The line of code that is going to calculate the factorial of the number 111130 is written before the line that will calculate the factorial of a significantly smaller number, i.e., 15. Python executes code line by line, but that is not happening here and that is due to multithreading.
Let’s understand the above code:
- A function is created that accepts one parameter and returns the factorial of that number.
- Next, we create two threads with only two parameters, i.e., target and args. The first thread has a large number for the argument of a factorial function and the second thread passes a way smaller number.
- threading.Thread class has a method start() that is used to start executing threads. We use that method to execute our threads.
- join() method waits for the completion of either thread then executes the next thread. As already discussed, context switching makes our threads look like they are running in parallel.
The join() method is also used to stop the execution of the program when a thread is completed executing.
Python Multithreading Example
Let’s take an example of a Python code in which we print thread name and the process associated with each task of the thread.
Example
Let’s understand the above code:
- current_thread().name method is used to get the name of the thread that is active at the time of the execution of this method.
- The name of the thread is passed as a parameter while creating/instantanizing the thread.
- All of the threads are using the same IDs because the code is waiting for the active thread to finish executing. This results in ID recycling of the threads.
Python Multithreading Vs Multiprocessing
Multithreading is often confused with the term multiprocessing. Although both the processes are used to increase the computing power of a computing system, they are completely different.
Multithreading: Processes are created according to the economical need. Multiple threads are created for a single process to increase the system’s performance.
Multiprocessing: Many processes are executed simultaneously. It is a process used in a system that has two or more processors.