Why Multithreading?

In the past, multithreading has been considered a powerful tool that is hard to handle (some call it the work of the devil). While there may be some truth to this, newer tools have made the job of the software developer much easier when creating parallel implementations of algorithms. At the same time, the necessity to use multiple threads to create performant applications has become more and more clear. Technologies like Hyperthreading and multiple core processors can only be used if processors have to schedule processing power to multiple, concurrently running processes or threads.

Event driven programs especially bear the issue of latency, which is more important for the user's impression of application performance than other factors. But the responsiveness of the user interface relies mainly on the ability of the application to process events, an ability that is much limited if the application is executing processing power expensive, lengthy operations. This leads, for example, to delayed or sluggish processing of necessary paint events. Even if this does not influence the total time necessary to perform the operation the user requested, it is annoying and not state of the art.

There are different approaches to solve this issue. The crudest one may be to process single or multiple events while performing a lengthy operation, which may or may not work sufficiently, but is at least sure to ruin all efforts of Separation of Concerns. Concerns can simply not be separated if the developer has to intermingle instructions with event handling where the kind of events that are processed are not known in advance.

Another approach is to use event-controlled asynchronous operations. This is sufficient in most cases, but still causes a number of issues. Any operation that carries the possibility of taking a long time or blocking may still stop event processing for a while. Such risks are hard to assess, and especially hard to test in laboratory environments where networks are fast and reliable and the system I/O load is generally low. Network operations may fail. Hard disks may be suspended. The I/O subsystem may be so busy that transferring 2 kByte may take a couple of seconds.

Processing events in objects that are executed in other threads is another approach. There are other issues that come with parallel programming, but it does ensure the main event loop returns as soon as possible. Usually this approach is combined with a state pattern to synchronize the GUI with the threaded event processing.

Which one of these approaches is suitable for a specific case has to be assessed by the application developers. There is no silver bullet. All have specific strengths, weaknesses and issues. The ThreadWeaver library provides the means to implement multithreaded job oriented solutions.

To create performant applications, the application designers have to leverage the functionality provided by the hardware platform as much as possible. While code optimizations only lead to slight improvement, application performance is usually determined by network and I/O throughput. The CPU time needed is usually negligible. At the same time, the different hardware subsystems usually are independent in modern architectures. Network, I/O and memory interfaces can transfer data all at the same time, and the CPU is able to process instructions while all these subsystems are busy. The modern computer is not a traditional uniprocessor (think of GPUs, too). This makes it necessary to use all these parallel subsystems at the same time as much as possible to actually use the possibilities modern hardware provides, which is very hard to achieve in a single thread.

Another very important issue is application processing flow. Especially GUI applications do not follow the traditional imperative programming pattern. Execution flow is more network-like, with chunks of code that depend on others to finish processing before they can touch their data. Tools to represent those networks to set up your applications order of execution are rare, and usually leave it to the developers to code the execution order of the instructions. This solutions are usually not flexible and do not adapt to the actual usage of the CPU nodes and computer subsystems. ThreadWeaver provides means to represent code execution dependencies and relies on the operating systems scheduler to actually distribute the work load. The result is an implementation that is very close to the original application semantics, and usually improved performance and scalability in different real-life scenarios.

The more tasks are handled in parallel, the more memory is necessary. There is a permanent CPU - memory tradeoff which limits the number of parallel operations to the extent where memory that needs to be swapped in and out slows down the operations. Therefore memory usage needs to be equalized to allow the processors to operate without being slowed down. This means parallel operations need to be scheduled to a limit to balance CPU and memory usage. ThreadWeaver provides the means to do that.

In general, ThreadWeaver tries to make the task of creating multithreaded, performant applications as simple as possible. Programmers should be relieved of synchronization, execution dependency and load balancing issues as much as possible. The API tends to be clean, extensible and easy to understand.

This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Aug 8 2022 04:15:41 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.