1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
|
Why Multithreading? {#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.
|