Mutithreaded Programing
& its application in Graphics
_____________________________________________________________________
Abstract: This article is forcus on structures, advantages
and limitation of concurrency implemented by multithreaded progamming,
and take multi-threaded Graphics in Java as one of their application
examples.
_________________________________________________
Part I < Concurrency Background> Advantages
& Limitation
*Advantages
Concurrency opens up design possibilities that are
impractical in sequential programs. Threads liberate you from the
limitations of code that invokes a method and then blocks, doing nothing
while waiting for a reply. Using threads, you can additionally trigger
new independent activities that run concurrently, with or without waiting
out their completion. Reasons to exploit threads include:
Reactive programming
Some programs are required to do more than one thing at a time, performing
each activity as a reactive response to some input. For example, a World
Wide Web browser may be simultaneously performing an http GET
request to get a Web page, playing an audio clip, displaying the number
of bytes received of some image, and engaging in an advisory dialog with
the user. While it is possible to program such systems in a single-threaded
manner by manually interleaving the different activities, this is complicated,
fragile, and error-prone. Reactive programs are easier to design and implement
using threads. In fact, the vast majority of design principles and patterns
described in this book are geared for use in reactive programs.
Availability
Concurrency allows you to maintain high availability of services. For example,
among the more common concurrent design patterns (seen in most internet
services and even in many applets) is to have one object serve as a gateway
interface to a service, handling each request by constructing a new thread
to asynchronously perform the associated actions. The gateway is able to
accept another request quickly. This helps avoid bottlenecks by draining
the communications network of pending messages. It can also improve the
fairness of access: new, quickly-serviceable requests do not have to wait
for old time-consuming requests to complete.
Controllability
Activities within threads can be suspended, resumed, and stopped by other
objects. This provides simplicity and flexibility not found in sequential
programming, where the desire to stop an activity (for a while, or forever)
and do something else is often difficult to implement.
Active objects
Software objects often model real objects. Most real objects display independent,
autonomous behavior. At least in some cases, the easiest way to program
this is to fire up a new thread whenever you create such an object.
Asynchronous messages
When one object sends a message to another, the first object does not always
care when the resulting action is performed. Threads allow the first object
to continue with its own activity without having to wait for unrelated
actions to complete.
Parallelism
On machines with multiple CPUs, concurrent programming can be used to exploit
available computing power to improve performance. Even without multiple
CPUs, interleaving activities in threads avoids delays associated with
time-consuming processing that need not complete before other activities
are started.
Required concurrency
Even if you do not explicitly intend to write concurrent programs, many
predefined Java support classes and run-time features operate in a concurrent
manner. These include the java.applet and
java.awt classes
for playing audio clips and displaying images, and the mechanisms that
cause every Java Applet to run in its own thread.
* Limitations
If concurrency is great, then you should use it everywhere. But you shouldn't.
The benefits of concurrency must be weighed against its costs in resource
consumption, efficiency, and program complexity:
Safety
When multiple threads are not completely independent, each can entail objects
sending messages to other objects that may also be involved in other threads.
All of these objects must utilize synchronization mechanisms or structural
exclusion techniques to ensure that they maintain consistent state. Attempts
to use multiple threads involving objects that were designed to work only
in sequential settings can lead to random-looking, hard-to-debug inconsistencies.
On the other hand, synchronization mechanics can add complexity to programs.
Liveness
Activities within concurrent programs may fail to be
live. That
is, one or more activities can simply stop, for any of a number of reasons;
for example because other activities are consuming all CPU cycles, or because
two different activities are
deadlocked, both endlessly waiting
for each other to continue.
Nondeterminism
Multithreaded activities can be arbitrarily interleaved. No two executions
of the same program need be identical. Activities requiring a lot of computation
may finish before those requiring practically no computation. This can
make multithreaded programs harder to predict, understand, and debug.
Threads versus method calls
Threads are not very useful for request/reply-style
programming. When one object must logically wait for a reply from
another in order to continue, the same thread should be used to implement
the entire request-execute-reply sequence. Constructing a new thread achieves
no benefit when there is no room for concurrency. Conversely, an activity
running as a thread cannot use the standard sequential invocation style
in which a client sends arguments, waits for them to be processed, and
then receives results in a reply. These effects can be obtained in threads,
but require special coding.
Objects versus activities
In essentially all object-oriented systems, there are many fewer asynchronously
executing concurrent activities than there are objects. Even from an active
object approach, it makes sense to create a new thread only when an invocation
actually generates a new asynchronous activity, not automatically whenever
constructing a new object that may or may not ever engage in asynchronous
activities.
Thread construction overhead
Constructing a thread and setting it in motion is typically slower and
more memory-intensive than constructing a normal object or invoking a method
on it. If an activity is only a matter of a few primitive statements, then
it is much faster just to invoke it via a method call than to use threads.
Context-switching overhead
When there are more active threads than there are CPUs, the Java run-time
system occasionally switches from running one activity to running another,
which also entails scheduling -- figuring out which thread to run
next.
Synchronization overhead
Java methods employing synchronization can be slower than those that do
not provide proper concurrency protection. And methods that must postpone
and resume actions depending on the current states of objects can be yet
more expensive. Between thread and synchronization overhead, concurrent
programs can run more slowly than sequential ones unless you have multiple
CPUs, and sometimes even if you do.
Threads versus processes
Activities that are intrinsically self-contained and sufficiently heavy
may be simpler to encapsulate into standalone programs. Standalone programs
can be accessed via system-level (concurrent) execution facilities or remote
invocation mechanisms rather than as multithreaded components of a single
process. (Although the borderlines can be slippery, a process is
usually defined as an active entity that maintains its own set of resources,
while a
thread uses the resources of its enclosing process.)
Part II Threads
Architecture and Benefit implementation
________________________________________________________________________________________________
What is a thread? Just as multitasking operating
systems can do more than one thing concurrently by running more than a
single process, a process can do the same by running more than a single
thread. Each thread is a different stream of control that can execute its
instructions independently, allowing a multithreaded process to perform
numerous tasks concurrently. One thread can run the GUI, while a second
thread does some I/O, while a third one performs calculations.
A thread is an abstract concept that comprises everything a computer
does in executing a traditional program. It is the program state that gets
scheduled on a CPU, it is the "thing" that does the work. If a process
comprises data, code, kernel state, and a set of CPU registers, then a
thread is embodied in the contents of those registers -- the program counter,
the general registers, the stack pointer, etc., and the stack. A thread,
viewed at an instant of time, is the state of the computation.
A thread and a process are conceptually related. But a process is a
kernel-level entity and includes such things as a virtual memory map, file
descriptors, user ID, etc., and each process has its own collection of
these. The only way for your program to access data in the process structure,
to query or change its state, is via a system call.
All parts of the process structure are in kernel space (Figure 1). A
user program cannot touch any of that data directly. By contrast, all of
the user code (functions, procedures, etc.) along with the data is in user
space, and can be accessed directly.
Figure 1 -- Relationship between a process and threads
A thread is a user-level entity. The thread structure is in user space
and can be accessed directly with the thread library calls, which are just
normal user-level functions. Note that the registers (stack pointer, program
counter, etc.) are all part of a thread, and each thread has its own stack,
but the code it is executing is not part of the thread. The actual code
(functions, routines, signal handlers, etc.) is global and can be executed
on any thread. In Figure 1, we show three threads (T1, T2, T3), along with
their stacks, stack pointers (SP), and programs counters (PC). T1 and T3
are executing the same function. This is a normal situation, just as two
different people can read the same road sign at the same time.
All threads in a process share the state of that process (see Figure
2). They reside in the exact same memory space, see the same functions,
see the same data. When one thread alters a process variable (say, the
working directory), all the others will see the change when they next access
it. If one thread opens a file to read it, all the other threads can also
read from it.
Figure 2 -- The process structure and the thread structures
When you write a multithreaded program, 99% of your programming is identical
to what it was before -- you spend you efforts in getting the program to
do its real work. The other 1% is spent in creating threads, arranging
for different threads to coordinate their activities, dealing with thread-specific
data, and signal masks. Perhaps 0.1% of your code consists of calls to
thread functions.
So here's the essential point about threads: They are user-level entities.
Virtually everything you do to a thread happens at user level with no system
calls involved. Because no system calls are involved, it's fast. There
are no kernel structures affected by the existence of threads in a program,
so no kernel resources are consumed -- threads are cheap. The kernel doesn't
even know that threads exist. (This is important. We're going to repeat
it about ten times.)
The value of using threads
There is really only one reason for writing MT programs -- you get
better programs, more quickly. If you're an ISV, you sell more software.
If you're developing software for your own in-house use, you simply have
better programs to use. The reason that you can write better programs is
because MT gives your programs and your programmers a number of significant
advantages over nonthreaded programs and programming paradigms.
A point to keep in mind here is that you are not replacing simple, nonthreaded
programs with fancy, complex, threaded ones. You are using threads only
when you need them to replace complex or slow nonthreaded programs. Threads
are really just one more way you have to make your programming tasks easier.
The main benefits of writing multithreaded programs are:
-
Performance gains from multiprocessing hardware (parallelism)
-
Increased application throughput
-
Increased application responsiveness
-
Enhanced process-to-process communications
-
Efficient use of system resources
-
The ability to make use of the inherent threadedness of distributed objects
-
There is one binary that runs on both uniprocessors and multiprocessors
-
The ability to create well-structured programs
-
There can be a single source for multiple platforms
The following sections elaborate further on these benefits.
Parallelism
Computers with more than one processor provide multiple simultaneous
points of execution (Figure 3). Multiple threads are an efficient way for
application developers to exploit the parallelism of the hardware. Different
threads can run on different processors simultaneously with no special
input from the user and no effort on the part of the programmer.
A good example of this is a process that does matrix multiplication.
A thread can be created for each available processor, allowing the program
to use the entire machine. The threads can then compute distinct elements
of the result matrix by doing the appropriate vector multiplication.
Figure 3 -- Different threads running on different processors
Throughput
When a traditional, single-threaded program requests a service from
the operating system, it must wait for that service to complete, often
leaving the CPU idle. Even on a uniprocessor, multithreading allows a process
to overlap computation with one or more blocking system calls (Figure 4).
Threads provide this overlap even though each request is coded in the usual
synchronous style. The thread making the request must wait, but another
thread in the process can continue. Thus, a process can have numerous blocking
requests outstanding, giving you the beneficial effects of doing asynchronous
I/O, while still writing code in the simpler synchronous fashion.
Figure 4 -- Two threads making overlapping system calls
Responsiveness
Blocking one part of a process need not block the whole process. Single-threaded
applications that do something lengthy when a button is pressed typically
display a "please wait" cursor and freeze while the operation is in progress.
If such applications were multithreaded, long operations could be done
by independent threads, allowing the application to remain active and making
the application more responsive to the user.
Communications
An application that uses multiple processes to accomplish its tasks
can be replaced by an application that uses multiple threads to accomplish
those same tasks. Where the old program communicated among its processes
through traditional IPC (interprocess communications) facilities (e.g.,
pipes or sockets), the threaded application can use the inherently shared
memory of the process. The threads in the MT process can maintain separate
IPC connections while sharing data in the same address space. A classic
example is a server program, which can maintain one thread for each client
connection (Figure 5). This provides excellent performance, simpler programming,
and effortless scalability.
Figure 5 -- Different client machines being handled by different
threads in a server program
System resources
Programs that use two or more processes to access common data through
shared memory are effectively applying more than one thread of control.
However, each such process must maintain a complete process structure,
including a full virtual memory space and kernel state. The cost of creating
and maintaining this large amount of state makes each process much more
expensive, in both time and space, than a thread. In addition, the inherent
separation between processes may require a major effort by the programmer
to communicate among the different processes or to synchronize their actions.
By using threads for this communication instead of processes, the program
will be easier to debug and can run much faster.
An application can create hundreds or even thousands of threads, one
for each synchronous task, with only minor impact on system resources.
Threads use a fraction of the system resources used by processes.
Distributed objects
With the first releases of standardized distributed objects and object
request brokers (coming in 1995), your ability to make use of these will
become increasingly important. Distributed objects are inherently multithreaded.
Each time you request an object to perform some action, it executes that
action in a separate thread (Figure 6). Object servers are an absolutely
fundamental element in distributed object paradigm, and those servers (as
discussed above) are inherently multithreaded.
Although you can make a great deal of use of distributed objects without
doing any MT programming, knowing what they are doing and being able to
create objects that are threaded will increase the usefulness of the objects
you do write.
Figure 6 -- Distributed objects running on distinct threads
Same binary for uniprocessors and multiprocessors
In most older parallel processing schemes, it was necessary to tailor
a program for the individual hardware configuration. With threads, this
customization isn't required because the MT paradigm works well irrespective
of the number of CPUs. A program can be compiled once, and it will run
acceptably on a uniprocessor, whereas on a multiprocessor it will just
run faster.
Program structure
Many programs are more efficiently structured with threads because
they are inherently concurrent. A traditional program that tries to do
many different tasks is crowded with lots of complicated code to coordinate
these tasks. A threaded program can do the same tasks with much less, far
simpler code. Multithreaded programs can be more adaptive to variations
in user demands than single- threaded programs (see Figure 7).
Figure 7 -- Simplified flow of control in complex applications
Part III Java Threads and their Application
in Graphics
_________________________________________________________________
Java threads allow a programmer to
build multitasking into their programs with great ease. Many languages
support multitasking through add-on libraries, yet Java includes threading
as part of the base language. Beginning to use threads (i.e., methods and
objects from the Thread
class) can be a fairly complex process because they require the programmer
to think about a lot more things in their program at once. However, knowing
how to utilize threads is a prerequisite for other important areas of Java
programming such as animation and network programming.
-
The Java Programming Tutorial defines a thread as a "single sequential
flow of control within a program"...
-
As such, each thread has a beginning, end and current point of execution
-
You can think of the programs you've written now having a single implicit
thread holding them together
-
We have already looked at methods which transparently create threads during
their operation; getImage (from Applet or Toolkit),
play and loop (from AudioClip)
-
The Java garbage collector is a continuously running Thread object
-
Thread objects share data in the program in which they live
-
Although threads run within a program, each Thread has
its own execution stack and program counter
-
Threads can also access resources allocated for the context of the program
which created them
-
Threads can have priorities assigned to them to make more important Threads
execute more often
-
Threads can be grouped together and addressed as a set if needed
Important Thread methods
Like Applet, Thread has a number of important methods
which are used and/or re-implemented in almost every program that intends
to use threading. Some of these methods even have the same name as their
equivalents in Applet, so be sure you know which methods you're
calling when you come to write your first threaded Applets.
-
run
-
This method should contain the code that executes whilst the Thread
is running. This method is almost always overridden by Thread
subclasses and often contains a loop which performs some part of the object's
duties during each iteration (e.g., display one frame of an animation,
etc)
-
start
-
Invoke this method on the Thread object to start the object running
-
stop
-
Stops this Thread object executing
-
sleep
-
This method suspends the current Thread execution for a
specified number of milliseconds. Calls to this method are often found
in run methods to allow other threads or processes to execute
for a period of time
Thread Applet Demonstration
Here is one applet demo for
showing responsive multi-threaded programming.
Here is a screen shot of the BasicThreadTest
applet
Thread state
The diagram below shows the various states a Thread object can
take, and which of the methods we've seen cause a change from one state
to another (other methods also cause state change, but this diagram has
been limited to the methods we've seen so far).
-
To query a Thread object to see which state it is in (which "set
of states" is more accurate), use the
isAlive method which returns a boolean true when
the Thread is Runnable or Dormant, and false if it is New or Dead
There is a lot more to be said about threads and their operation. This
brief discussion is merely to provide sufficient background knowledge of
threads prior to looking at animation and threaded servers.
Animation
Like most aspects of multimedia in Java, animation can be accomplished
quite simply, but various degrees of optimization are needed to really
use animation efficiently in conjunction with other parts of Java programs.
Using animation properly involves the knowledge and use of threads to concurrently
perform other tasks whilst the animation is running. The Applet
running below is a fairly simple animation that could use some customization
to improve its performance and user-friendliness.
sourceCode 1 *2