Implementing Python Multithreading (Parallel Execution)

힘센캥거루
2025년 7월 2일(수정됨)
63
python
Implementing Python Multithreading (Parallel Execution)-1

When coding with Python, you often encounter issues when implementing a GUI like tkinter.

When a button is pressed to execute a specific function, the GUI freezes.

So, I decided to solve this using multithreading.

1. Multithreading? Multiprocessing?

Implementing Python Multithreading (Parallel Execution)-2

Multithreading is a technique that runs multiple threads of execution simultaneously within a single process.

For example, if a function triggered by a button takes a long time to execute while a tkinter GUI is running on the main thread, the GUI will freeze.

This is similar to applications like the admission programs everyone at school knows, such as UNIV.

Implementing Python Multithreading (Parallel Execution)-3

Opening a specific window makes the original window freeze, making the GUI very inconvenient for users.

By running the function executed by a button click in a separate thread, the GUI continues to run on the main thread, and the function executes in another thread, preventing the screen from freezing.

Implementing Python Multithreading (Parallel Execution)-4

On the other hand, multiprocessing creates and executes completely separate processes.

Threads execute within a single process sharing memory, while multiprocessing duplicates processes to execute in independent memory spaces.

It's advantageous for compute-intensive tasks as multiple CPU cores can be utilized, but in GUI programs, it requires inter-process communication, making it not as straightforward as threading.

Category

Multithreading

Multiprocessing

Execution Method

Multiple threads within a single process

Multiple independent processes

Memory

Shared memory space

Independent memory space (duplication)

CPU Utilization

Limited for CPU-bound tasks due to GIL (Global Interpreter Lock)

Multiple CPU cores can be used

Relationship with GUI

Suitable for preventing GUI freezing

Separate process requiring communication

Usage Examples

Function execution after tkinter button click

Image processing, large-scale calculations, etc.

The reason multiprocessing is possible in Python is that processes can be duplicated or newly created and executed using the multiprocessing library.

However, since memory is independent per process, Queue, Pipe, shared memory, etc., must be used to share data.

That is, for GUI programs where the user interface must not freeze, it is better to appropriately use multithreading, while for large-scale computation tasks, multiprocessing is suitable.

2. Multithreading Code Example

Let's assume we have the following code.

This code prints out hello and hi for the number of input numbers.

import time

def printHello(num):
    for i in range(num):
        print(f'hello-{i}')
        time.sleep(1)
        
def printHi(num):
    for i in range(num):
        print(f'hi-{i}')
        time.sleep(1)
        
printHello(3)
printHi(2)

When executed as in the bottom line, Hello comes out 3 times and then Hi comes out 2 times.

Since Python is a synchronous execution function, the next function waits until the current function ends.

hello-0
hello-1
hello-2
hi-0
hi-1

Now let's implement this with multithreading.

The functions printHello and printHi are the same as above, and a simple explanation of the code is included as comments.

import threading
import time

...
        
hello = threading.Thread(target=printHello, args=(3, ), daemon=True)
hello.start()
hi = threading.Thread(target=printHi, args=(2, ), daemon=True)
hi.start()
hello.join()
hi.join()
print('Task completed')

Executing this way, hello and hi are printed simultaneously at 1-second intervals.

After the last hello is printed, Task completed is printed.

hello-0hi-0
hello-1hi-1
hello-2
Task completed

Here are the important parameters of Thread.

It's usually good to set daemon to true for peace of mind.

Option

Description

Example

target

The target function to execute

target=my_function

args

Parameters to pass to the function (tuple)

args=("parameter1", 2)

daemon

Whether it's a daemon thread. If True, it ends with the main thread

daemon=True

3. Instead of threading, use futures?

Threading isn't very difficult to use, but it requires handling processes like daemon, join, etc.

Since Python 3.2, it supports a library called concurrent.futures, which can be used to implement this a bit more simply.

import time
from concurrent.futures import ThreadPoolExecutor

def printHello(num, name):
    for i in range(num):
        print(f'{name}-hello-{i}')
        time.sleep(1)

def printHi(num, name):
    for i in range(num):
        print(f'{name}-hi-{i}')
        time.sleep(1)

executor = ThreadPoolExecutor(max_workers=2)
 
future1 = executor.submit(printHello, 5, "Alice")
future2 = executor.submit(printHi, 5, "Bob")

future1.result()
future2.result()
        
print("All tasks completed")

You only need to pass the function and the parameters to be included inside the function in order to submit.

The function execution starts at the time of submit, and when result is called, it waits until the function ends and gets the return value.

If result is not called, the program may end before the main thread waits.

By default, it is declared inside as a daemon, so no separate declaration is needed.

It's a bit more intuitive than threading.

You can also wrap it in a with statement to prevent memory leaks as below.

with ThreadPoolExecutor(max_workers=2) as executor:
    future1 = executor.submit(printHello, 5, "Alice")
    future2 = executor.submit(printHi, 5, "Bob")
    
    future1.result()
    future2.result()

The with statement automatically calls executor.shutdown(wait=True) to safely terminate threads.

4. Example of Simultaneously Running GUI and Function

Now let's run a simple GUI and a function simultaneously.

We'll use customtkinter for the GUI.

The function will simply use printHello and printHi as before.

Below is how I implemented the class.

from concurrent.futures import ThreadPoolExecutor
import time
import customtkinter as ctk

class helloHiWindow:
    def __init__(self):
        self.hello = 5
        self.hi = 6
        self.app = ctk.CTk()
        self.executor = ThreadPoolExecutor(max_workers=2)
        self.customWindow()
        
    def run(self):    
        self.app.mainloop()
        
    def customWindow(self):
        self.app.title("Hello! Hi!")
        self.app.geometry("300x200")
        button1 = ctk.CTkButton(self.app, text="Hello!", height=10, command=self.startHello)
        button1.pack(pady=20)
        button2 = ctk.CTkButton(self.app, text="Hi!", height=10, command=self.startHi)
        button2.pack(pady=20)
        
    def startHello(self):
        hello = self.executor.submit(self.printHello)
        
    def startHi(self):
        hi = self.executor.submit(self.printHi)
                
    def printHello(self):
        for i in range(self.hello):
            print(f'hello-{i}')
            time.sleep(1)
            
    def printHi(self):
        for i in range(self.hi):
            print(f'hi-{i}')
            time.sleep(1)
            
            
if __name__ == "__main__":
    app = helloHiWindow()
    app.run()

If you're using a MacBook, you need to install the library using brew install python-tk.

After creating it this way and running the window, the window appears as shown below.

Clicking the buttons will print output.

Implementing Python Multithreading (Parallel Execution)-5

Even if you click the buttons randomly, the existing window doesn't freeze.

This is because the window runs on the main thread and the print function runs as a sub-thread.

In this way, you can make the window not freeze while allowing the function to run.

5. So, this implementation isn't real multithreading...?

Python has something called the GIL (Global Interpreter Lock), so more than one thread can't run simultaneously on the CPU.

It's said to be created for enhancing speed and ensuring development stability in Python.

However, the code above seems to run simultaneously when you execute it.

Implementing Python Multithreading (Parallel Execution)-6

This is because Python code quickly switches in and out within a single thread.

It's similar to the event loop in JavaScript.

A function like time.sleep(1) does not occupy the CPU and waits, processing other necessary functions in the meantime.

This makes it look as if there are multiple threads existing, while actually there's only one.

6. Conclusion

Initially, when I learned Python, the concept of multithreading (multitasking) felt somewhat new and difficult.

So, I simply used the terminal window, but as I started moving into the development phase, the black terminal window started feeling too cumbersome, so I first implemented a GUI with customtkinter.

Going through that process, I came to understand multithreading more clearly.

In programming, you gain more by solving a project than from lectures or classes.

I'll continue to carry out projects steadily in the future.

댓글을 불러오는 중...