PyQt graphical user interface (GUI) applications have a main thread of execution that runs the event loop and GUI. If you launch a long-running task in this thread, then your GUI will freeze until the task terminates. During that time, the user won’t be able to interact with the application, resulting in a bad user experience. Luckily, PyQt’s QThread class allows you to work around this issue.
In this tutorial, you’ll learn how to:
- Use PyQt’s
QThreadto prevent freezing GUIs - Create reusable threads with
QThreadPoolandQRunnable - Manage interthread communication using signals and slots
- Safely use shared resources with PyQt’s locks
- Use best practices for developing GUI applications with PyQt’s thread support
For a better understanding of how to use PyQt’s threads, some previous knowledge of GUI programming with PyQt and Python multithreaded programming would be helpful.
Free Bonus: Get a sample chapter from Python Basics: A Practical Introduction to Python 3 to see how you can go from beginner to intermediate in Python with a complete curriculum, up to date for Python 3.9.
Freezing a GUI With Long-Running Tasks
Long-running tasks occupying the main thread of a GUI application and causing the application to freeze is a common issue in GUI programming that almost always results in a bad user experience. For example, consider the following GUI application:

Say you need the Counting label to reflect the total number of clicks on the Click me! button. Clicking the Long-Running Task! button will launch a task that takes a lot of time to finish. Your long-running task could be a file download, a query to a large database, or any other resource-intensive operation.
Here’s a first approach to coding this application using PyQt and a single thread of execution:
import sys
from time import sleep
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)
class Window(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.clicksCount = 0
self.setupUi()
def setupUi(self):
self.setWindowTitle("Freezing GUI")
self.resize(300, 150)
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
# Create and connect widgets
self.clicksLabel = QLabel("Counting: 0 clicks", self)
self.clicksLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.stepLabel = QLabel("Long-Running Step: 0")
self.stepLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.countBtn = QPushButton("Click me!", self)
self.countBtn.clicked.connect(self.countClicks)
self.longRunningBtn = QPushButton("Long-Running Task!", self)
self.longRunningBtn.clicked.connect(self.runLongTask)
# Set the layout
layout = QVBoxLayout()
layout.addWidget(self.clicksLabel)
layout.addWidget(self.countBtn)
layout.addStretch()
layout.addWidget(self.stepLabel)
layout.addWidget(self.longRunningBtn)
self.centralWidget.setLayout(layout)
def countClicks(self):
self.clicksCount += 1
self.clicksLabel.setText(f"Counting: {self.clicksCount} clicks")
def reportProgress(self, n):
self.stepLabel.setText(f"Long-Running Step: {n}")
def runLongTask(self):
"""Long-running task in 5 steps."""
for i in range(5):
sleep(1)
self.reportProgress(i + 1)
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())
In this Freezing GUI application, .setupUi() creates all the required graphical components for the GUI. A click on the Click me! button calls .countClicks(), which makes the text of the Counting label reflect the number of button clicks.
Note: PyQt was first developed to target Python 2, which has an exec keyword. To avoid a name conflict on those earlier versions of PyQt, an underscore was added to the end of .exec_().
Even though PyQt5 targets only Python 3, which doesn’t have an exec keyword, the library provides two methods to start an application’s event loop:
.exec_().exec()
Both variations of the method work the same, so you can use either one in your applications.
Clicking the Long-Running Task! button calls .runLongTask(), which performs a task that takes 5 seconds to complete. This is a hypothetical task that you coded using time.sleep(secs), which suspends the execution of the calling thread for the given number of seconds, secs.
In .runLongTask(), you also call .reportProgress() to make the Long-Running Step label reflect the progress of the operation.
Does this application work as you intend? Run the application and check out its behavior:

When you click the Click me! button, the label shows the number of clicks. However, if you click the Long-Running Task! button, then the application becomes frozen and unresponsive. The buttons no longer respond to clicks and the labels don’t reflect the application’s state.
After five seconds, the application’s GUI gets updated again. The Counting label shows ten clicks, reflecting five clicks that occurred while the GUI was frozen. The Long-Running Step label doesn’t reflect the progress of your long-running operation. It jumps from zero to five without showing the intermediate steps.
Note: Even though your application’s GUI freezes during the long-running task, the application still registers events such as clicks and keystrokes. It’s just unable to process them until the main thread gets released.
The application’s GUI freezes as a result of a blocked main thread. The main thread is busy processing a long-running task and doesn’t immediately respond to the user’s actions. This is an annoying behavior because the user doesn’t know for sure if the application is working correctly or if it’s crashed.
Fortunately, there are some techniques you can use to work around this issue. A commonly used solution is to run your long-running task outside of the application’s main thread using a worker thread.
In the sections below, you’ll learn how to use PyQt’s built-in thread support to solve the issue of unresponsive or frozen GUIs and provide the best possible user experience in your applications.
Multithreading: The Basics
Sometimes you can divide your programs into several smaller subprograms, or tasks, that you can run in several threads. This might make your programs faster, or it might help you improve the user experience by preventing your programs from freezing while executing long-running tasks.
A thread is a separate flow of execution. In most operating systems, a thread is a component of a process, and processes can have multiple threads executing concurrently. Each process represents an instance of a program or application that is currently running in a given computer system.
You can have as many threads as you need. The challenge is determining the right number of threads to use. If you’re working with I/O-bound threads, then the number of threads will be limited by your available system resources. On the other hand, if you’re working with CPU-bound threads, then you’ll benefit from having a number of threads that is equal to or less than the number of CPU cores in your system.
Building programs that are capable of running multiple tasks using different threads is a programming technique known as multithreaded programming. Ideally, with this technique, several tasks run independently at the same time. However, this isn’t always possible. There are at least two elements that can prevent a program from running several threads in parallel:
- The central processing unit (CPU)
- The programming language
For example, if you have a single-core CPU machine, then you can’t run multiple threads at the same time. However, some single-core CPUs can simulate parallel thread execution by allowing the operating system to schedule the processing time between multiple threads. This makes your threads appear to run in parallel even though they’re really running one at a time.
On the other hand, if you have a multi-core CPU machine or a computer cluster, then you might be able to run multiple threads at the same time. In this case, your programming language becomes an important factor.
Some programming languages have internal components that actually prohibit the real execution of multiple threads in parallel. In these cases, threads just appear to run in parallel because they take advantage of the task scheduling system.
Multithreaded programs are usually more difficult to write, maintain, and debug than single-threaded programs because of the complexity related to sharing resources between threads, synchronizing data access, and coordinating thread execution. This can cause several problems:
-
Race condition is when the application’s behavior becomes nondeterministic due to the unpredictable order of events. It’s often the result of two or more threads accessing a shared resource without proper synchronization. For example, reading and writing memory from different threads can lead to a race condition if the reading and writing operations are performed in the wrong order.
-
Deadlock happens when threads wait indefinitely for a locked resource to be freed. For example, if a thread locks a resource and doesn’t unlock it after use, then other threads won’t be able to use that resource and will wait indefinitely. Deadlocks can also happen if thread A is waiting for thread B to unlock a resource, and thread B is waiting for thread A to unlock a different resource. Both threads will end up waiting forever.
-
Livelock is a situation in which two or more threads repeatedly act in response to each other’s actions. Livelocked threads are unable to make further progress on their specific task because they’re too busy responding to each other. However, they’re not blocked or dead.
-
Starvation happens when a process never gains access to the resources it needs for finishing its work. For example, if you have a process that can’t gain CPU time access, then the process is starving for CPU time and can’t do its work.
When building multithreaded applications, you need to be careful to protect your resources from concurrent writing or state modification access. In other words, you need to prevent multiple threads from accessing a given resource at the same time.
A wide range of applications can benefit from using multithreaded programming in at least three ways:
- Making your applications faster by taking advantage of multi-core processors
- Simplifying the application structure by dividing it into smaller subtasks
- Keeping your application responsive and up to date by offloading long-running tasks to worker threads
In Python’s C implementation, also known as CPython, threads don’t run in parallel. CPython has a global interpreter lock (GIL), which is a lock that basically allows only one Python thread to run at a time.
This can negatively affect the performance of threaded Python applications because of the overhead that results from the context switching between threads. However, multithreading in Python can help you solve the problem of freezing or unresponsive applications while processing long-running tasks.
Multithreading in PyQt With QThread
Qt, and therefore PyQt, provides its own infrastructure to create multithreaded applications using QThread. PyQt applications can have two different kinds of threads:
- Main thread
- Worker threads
The application’s main thread always exists. This is where the application and its GUI run. On the other hand, the existence of worker threads depends on the application’s processing needs. For example, if your application commonly runs heavy tasks that take a lot of time to finish, then you might want to have worker threads to run those tasks and avoid freezing the application’s GUI.
The Main Thread
In PyQt applications, the main thread of execution is also known as the GUI thread because it handles all widgets and other GUI components. Python starts this thread when you run the application. The application’s event loop runs in this thread after you call .exec() on the QApplication object. This thread handles your windows, dialogs, and also the communication with the host operating system.
By default, any event or task that takes place in the application’s main thread, including the user’s events on the GUI itself, will run synchronously, or one task after another. So, if you start a long-running task in the main thread, then the application needs to wait for that task to finish, and the GUI becomes unresponsive.
It’s important to note that you must create and update all your widgets in the GUI thread. However, you can execute other long-running tasks in worker threads and use their results to feed the GUI components of your application. This means that GUI components will act as consumers that are fed information from the threads performing the actual work.
Worker Threads
You can create as many worker threads as you need in your PyQt applications. Worker threads are secondary threads of execution that you can use to offload long-running tasks from the main thread and prevent GUI freezing.
You can create worker threads using QThread. Each worker thread can have its own event loop and support PyQt’s