Simplifying Parallelism Complexity
In this chapter, we will drastically simplify the creati on of new parallelized
code avoiding some advanced concurrent programming diffi culti es. Reading this
chapter and following the exercises we shall:
-
Learn to combine single-threaded code with multi threaded code
-
Use of object-oriented design patt erns to simplify the creati on of
parallelized code
-
Solve various problems to specialize in segmentati on algorithms and achieve
thread affi nity
-
Encapsulate multi threaded algorithms to create high-performance and safer
independent pieces
-
Learn to avoid problems with design instead of solving them using very
difficult-to-apply algorithms
|
This chapter excerpt from
C# 2008 and 2005 Threaded Programming: Beginner's Guide by Gastón
C. Hillar, is printed with permission from Packt
Publishing, Copyright 2007.
|
Specializing the algorithms for segmentation with classes
So far, we have been developing applicati ons that split work into multi ple
independent jobs and created classes to generalize the algorithms for
segmentati on. We simplifi ed the creati on of segmented and parallelized
algorithms, generalizing behaviors to simplify our code and to avoid repeati ng
the same code on every new applicati on. However, we did not do that using
inheritance, a very powerful object-oriented capability that simplifi es code
re-use. C# is an object-oriented programming language that supports inheritance
and off ers many possibiliti es to specialize behaviors to simplify our code
and to avoid some synchronizati on problems related to parallel programming.
How can we use C# object-oriented capabiliti es to defi ne specifi c segmented
algorithms prepared for running each piece in an independent thread using
ParallelAlgorithm and ParallelAlgorithmPiece as the base classes?
The answer is very simple—by using inheritance and the factory method class
creati onal patt ern (also known as virtual constructor). Thus, we can advance
into creati ng a complete framework to simplify the algorithm opti mizati on
process. Again, we can combine multi threading with object-oriented capabiliti
es to reduce our development ti me and avoid synchronizati on problems.
You made the necessary changes to the ParallelAlgorithmPiece and the
ParallelAlgorithm classes to possibly fi nd planets similar to Mars in the
images corresponding to diff erent galaxies.
NASA's CIO was impressed with your parallel programming capabiliti es.
Nevertheless, he is an object-oriented guru, and he gave you the advice to
apply the factory method patt ern to specialize the parallel algorithm classes
in each new algorithm. That could make the code simpler, more re-usable, and
easier to maintain.
He asked you to do so. The NASA scienti sts would then bring you another huge
image processing challenge for your parallel programming capabiliti es—a
sunspot analyzer. If you resolve this problem using the factory method patt ern
or something like that, he will hire you! However, be careful, because you must
avoid some synchronizati on problems!
First, we are going to create a new project with tailored versions of the
ParallelAlgorithmPiece and ParallelAlgorithm classes. This way, later, we will
be able to inherit from these classes and apply the factory method patt ern to
specialize in parallel algorithms:
-
Create a new C# Project using the Windows Forms Applicati on template in Visual
1. Studio or Visual C# Express. Use SunspotsAnalyzer as the project's name.
-
Open the code for 2. Program.cs.
-
Replace the line 3. [STAThread] with the following line (before the Main method
declarati on):
[MTAThread]
-
Copy the fi le that contains the original code of the 4. ParallelAlgorithmPiece
and the ParallelAlgorithm classes (ParallelAlgorithm.cs) and include them in
the project.
-
Add the 5. abstract keyword before the declarati ons of the
ParallelAlgorithmPiece and the ParallelAlgorithm classes, as shown in the
following lines (we do not want to create instances directly from these
abstract classes):
abstract class ParallelAlgorithmPiece
abstract class ParallelAlgorithm
-
Change the 6. ThreadMethod method declarati on in the ParallelAlgorithmPiece
class (add the abstract keyword to force us to override it in subclasses):
public abstract void ThreadMethod(object poThreadParameter);
-
Add the following public abstract method to create each parallel algorithm
piece in 7. the ParallelAlgorithm class (the key to the factory method patt
ern):
public abstract ParallelAlgorithmPiece
CreateParallelAlgorithmPiece(int priThreadNumber);
-
Add the following constructor with a parameter to the ParallelAlgorithmPiece
class:
public ParallelAlgorithmPiece(int priThreadNumberToAssign)
{
priThreadNumber = priThreadNumberToAssign;
}
-
Copy the original code of the 9. ParallelAlgorithmPiece class CreatePieces
method and paste it in the ParallelAlgorithm class (we move it to allow creati
on of parallel algorithm pieces of diff erent subclasses). Replace the
lloPieces[i].priBegin and lloPieces[i].priEnd private variables' access with
their corresponding public properti es access lloPieces[i].piBegin and
lloPieces[i].piEnd.
-
Change the new 10. CreatePieces method declarati on in the ParallelAlgorithm
class (remove the stati c clause and add the virtual keyword to allow us to
override it in subclasses and to access instance variables):
public virtual List<ParallelAlgorithmPiece>
CreatePieces(long priTotalElements, int priTotalParts)
-
Replace the line 11. lloPieces[i] = new ParallelAlgorithmPiece(); in the
CreatePieces method declarati on in the ParallelAlgorithm class with the
following line of code (now the creati on is encapsulated in a method, and
also, a great bug is corrected, which we will explain later):
lloPieces.Add(CreateParallelAlgorithmPiece(i));
-
Toi di lang thang lan trong bong toi buot gia, ve dau khi da mat em roi? Ve dau
khi bao nhieu mo mong gio da vo tan... Ve dau toi biet di ve dau?
http://nhatquanglan1.0catch.com
//lloPieces[i].piThreadNumber = i;
-
Replace the line 13. prloPieces = ParallelAlgorithmPiece.
CreatePieces(priTotalElements, priTotalParts); in the CreateThreads method
declarati on in the ParallelAlgorithm class with the following line of code
(now the creati on is done in the new CreatePieces method):
prloPieces = CreatePieces(priTotalElements, priTotalParts);
-
Change the 14. StartThreadsAsync method declarati on in the ParallelAlgorithm
class (add the virtual keyword to allow us to override it in subclasses):
public virtual void StartThreadsAsync()
-
Change the 15. CollectResults method declarati on in the ParallelAlgorithm
class (add the abstract keyword to force us to override it in subclasses):
public abstract void CollectResults();
What just happened?
The code required to create subclasses to implement algorithms, following a
variati on of the factory method class creati onal patt ern, is now held in the
ParallelAlgorithmPiece and ParallelAlgorithm classes.
Thus, when we create new classes that will inherit from these two classes, we
can easily implement a parallel algorithm. We must just fi ll in the gaps and
override some methods, and we can then focus on the algorithm problems instead
of working hard on the splitti ng techniques.
We also solved some bugs related to the previous versions of these classes.
Defining the class to instantiate
One of the main problems that arise when generalizing an algorithm is that the
generalized code needed to coordinate the parallel algorithm must create
instances of the subclasses that represent the pieces.
Using the concepts introduced by the factory method class creati onal patt ern,
we solved this problem with great simplicity. We made the necessary changes to
the ParallelAlgorithmPiece and ParallelAlgorithm classes to implement a variati
on of this design patt ern.
First, we added a constructor to the ParallelAlgorithmPiece class with the
thread or piece number as a parameter. The constructor assigns the received
value to the priThreadNumber private variable, accessed by the piThreadNumber
property:
public ParallelAlgorithmPiece(int priThreadNumberToAssign)
{
priThreadNumber = priThreadNumberToAssign;
}
The subclasses will be able to override this constructor to add any additi onal
initi alizati on code.
We had to move the CreatePieces method from the ParallelAlgorithmPiece class to
the ParallelAlgorithm class. We did this because each ParallelAlgorithm
subclass will know which ParallelAlgorithmPiece subclass to create for each
piece representati on. Thus, we also made the method virtual, to allow it to be
overridden in subclasses. Besides, now it is an instance method and not a
static one.
There was an intenti onal bug left in the previous CreatePieces method. As you
must master lists and collecti ons management in C# in order to master parallel
programming, you should be able to detect and solve this litt le problem. The
method assigned the capacity, but did not add elements to the list. Hence, we
must use the add method using the result of the new
CreateParallelAlgorithmPiece method.
lloPieces.Add(CreateParallelAlgorithmPiece(i));
The creati on is now encapsulated in this method, which is virtual, and allows
subclasses to override it. The original implementati on is shown in the
following lines:
public virtual ParallelAlgorithmPiece CreateParallelAlgorithmPiece (int
priThreadNumber)
{
return (new
ParallelAlgorithmPiece(priThreadNumber));
}
It returns a new ParallelAlgorithmPiece instance, sending the thread or piece
number as a parameter.
Overriding this method, we can return instances of any subclass of
ParallelAlgorithmPiece. Thus, we let the ParallelAlgorithm subclasses decide
which class to instantiate.
We made additi onal changes needed to keep conceptual integrity with this new
approach for the two classes that defi ne the behavior of a parallel algorithm
that splits work into pieces using multi threading capabiliti es.
Preparing the classes for inheritance
Apart from implementi ng a variati on of the factory method design patt ern, we
had to prepare the classes for inheritance. We must override methods in order
to create specialized classes. Therefore, we had to change some methods'
declarati ons to make them virtual.
We must override the following methods in a ParallelAlgorithmPiece subclass:
-
The constructor
This is used to call the base constructor and append any additi onal initi
alizati on code that is needed. Remember that constructors do not require the
virtual keyword.
-
ThreadMethod This is the code that is going to be run by
the thread.
We must override the following methods in a ParallelAlgorithm subclass:
-
The constructor This is used to call the base
constructor and append any additi onal initi alizati on code that is needed.
Remember that constructors do not require the virtual keyword.
-
CreateParallelAlgorithmPiece This is used to return a
new instance of a specifi c ParallelAlgorithmPiece subclass.
-
StartThreadsAsync This is used to append any additi
onal code needed before or aft er starti ng the threads with an asynchronous
executi on.
-
CollectResults This is used to collect the results left
in each ParallelAlgorithmPiece subclass instance.
Once the classes are prepared for a common parallel algorithm with job splitti
ng capabiliti es, we must create the subclasses and fi ll in the gaps.
Now, you have just created two subclasses and programmed the UI management code
in order to create any split job, multi threaded algorithm. The NASA scienti
sts have a keen interest in analyzing sunspots. However, the colors off ered by
the original, very high-resoluti on images make it diffi cult to generate the
stream inputs necessary for the servers to make graphs about the sunspots'
evoluti on.
They want you to help them in developing a simple applicati on that has to
invert the colors of many of these huge images, because there is no soft ware
that is able to work with this huge number of pixels. In the following image,
you can see an image taken by Hinode's Solar Opti cal Telescope on November 20,
2006:
This image reveals the structure of the solar magneti c fi eld rising verti
cally from a sunspot outward into the solar atmosphere. A sunspot is an area of
strong magneti c field.
You have to work on a very fast and very effi cient image color inverter,
capable of changing the pixel colors to their opposite in the huge image, to
make them capable of streaming to the stati sti cs servers. NASA's CIO wants
you to use a very fi ne-tuned multi threading applicati on based on subclasses
to demonstrate your object-oriented capabiliti es combined with parallel
programming knowledge. Of course, the algorithm must be capable of working with
as many threads as the number of cores available in the computer in which the
sunspot analyzer algorithm is being executed.
First, we are going to create a subclass of the ParallelAlgorithmPiece class and
fi ll in the gaps, overriding the necessary methods to defi ne the code to be
executed for each piece. Thus, we are going to represent each piece of the
sunspot analyzer parallel algorithm:
-
Stay in the project, SunspotsAnalyzer.
-
Create a new class, SunspotAnalyzerPiece (a subclass of ParallelAlgorithmPiece)
using the following declarati on:
class SunspotAnalyzerPiece : ParallelAlgorithmPiece
-
Add the following lines of code at the beginning (as we are including the
classes in a new applicati on that does not know anything about drawing, we are
going to use the System.Drawing classes):
using System.Drawing;
-
Add the following private variable:
// The resulting bitmap
private Bitmap proBitmap;
-
Add the following property to access the private variable (we want to create
a compact and reliable class):
public Bitmap poBitmap
{
set
{
proBitmap = value;
}
get
{
return proBitmap;
}
}
-
Add a new constructor with a parameter that calls the base constructor:
public SunspotAnalyzerPiece(int priThreadNumberToAssign)
: base(priThreadNumberToAssign)
{
// Add any
necessary additional instructions
}
-
Override the ThreadMethod method with the code that is going to be run by the
thread:
public override void ThreadMethod(object poThreadParameter)
{
// This is the code that is going to be run by
the thread
// It has access to all the instance variable of
this class
// It receives the instance as a parameter
// We must typecast poThreadParameter to
SunSpotAnalyzerPiece (inherited
from ParallelAlgorithmPiece) to gain access
to its members
SunspotAnalyzerPiece loPiece;
loPiece =
(SunspotAnalyzerPiece)poThreadParameter;
// Retrieve the thread number received in object
poThreadParameter, in piThreadNumber property
long liPieceNumber = loPiece.piThreadNumber;
// The pixel matrix (bitmap) row number (Y)
int liRow;
// The pixel matrix (bitmap) col number (X)
int liCol;
// The pixel color
Color loPixelColor;
// Iterate through each pixel matrix (bitmap)
row
for (liRow = 0; liRow < proBitmap.Height;
liRow++)
{
// Iterate
through each pixel matrix (bitmap) col
for (liCol =
0; liCol < proBitmap.Width; liCol++)
{
// Get the pixel Color
for liCol and liRow
loPixelColor =
proBitmap.GetPixel(liCol, liRow);
// Change the pixel
color (invert the color)
proBitmap.SetPixel(liCol,
liRow, Color.FromArgb((
Int32.MaxValue - loPixelColor.ToArgb())));
}
}
}
What just happened?
The code to defi ne the work done in the pieces of the algorithm is now held in
the new SunspotAnalyzerPiece class (a subclass of ParallelAlgorithmPiece). The
instances of this class are real independent parts of the sunspot analyzer
algorithm. Everything we need to access from each thread for the algorithm
resides in the instance of this class.
Creating a complete piece of work
First, we added the variables and properti es needed for the piece of work. We
will be working with a porti on of a bitmap. So, we defi ned the proBitmap
private variable and its poBitmap property.
Then, we defi ned a constructor with the same parameter used in the base class:
public SunspotAnalyzerPiece(int priThreadNumberToAssign)
: base(priThreadNumberToAssign)
This declarati on calls the base constructor (ParallelAlgorithmPiece
constructor) with the priThreadNumberToAssign received as a parameter, and
allows us to add additi onal initi alizati on code if any. We are therefore
overriding the constructi on, but calling the base class constructor.
Then, we had to override the ThreadMethod method. This is the method that will
run the code for each created thread (for each piece). We declared it with the
same parameters as those in the base class:
public override void ThreadMethod(object poThreadParameter)
It receives the well-known object poThreadParameter parameter. However, it will
be the corresponding instance of the SunspotAnalyzerPiece class assigned to
that thread. Thus, casti ng the object poThreadParameter parameter to
SunspotAnalyzerPiece, we will have a reference to the instance of
SunspotAnalyzerPiece and hence, full access to the instance variables that are
exclusive for the thread and completely independent of the others:
SunspotAnalyzerPiece loPiece;
loPiece = (SunspotAnalyzerPiece)poThreadParameter;
We defi ne a loPiece local variable with the SunspotAnalyzerPiece type, in order
to avoid having to cast to SunspotAnalyzerPiece many ti mes. The code is easier
to understand this way.
Writing the code for a thread in an instance method
Developing multi threaded algorithms using stati c members is easy because most
types of public stati c members are thread-safe. Nevertheless, writi ng
real-life applicati ons based on this rule is impossible.
You will fi nd this text in Microsoft Visual Studio helpful in many cases:
All public stati c (Shared in Visual Basic) members of this type are
thread-safe. No instance member is guaranteed to be thread-safe.
We do need instance members for real-life applicati ons. They create risks when
we do not follow certain rules when programming multi threaded applicati ons.
However, it is the only way to create real-life applicati ons and parallel
algorithms that do something interesti ng—not just display numbers in the
console output.
Hence, we write the code to be run in each thread in an instance method of the
SunspotAnalyzerPiece class, which has access to those instance variables. It is
a very independent block of code. Of course, it has some problems and trade-off
s, but its main goal is to make it possible to split a huge task into many
concurrent pieces.
Once the code retrieves its instance, it iterates through each pixel matrix
(bitmap rows and columns) and inverts the colors in the following line of code:
proBitmap.SetPixel(liCol, liRow, Color.FromArgb((Int32.MaxValue -
loPixelColor.ToArgb())));
We take the maximum value of the Int32 data type to obtain the diff erence
between this and the pixel color converted to an Int32 Alpha, Red, Green, Blue
value. It is simple, pure mathemati cs.
As we are working in our piece of the bitmap, we begin the row number in 0, and
take into account its Height property:
for (liRow = 0; liRow < proBitmap.Height; liRow++)
We have the pieces (instances of SunspotAnalyzerPiece), but now we must create
the subclass to create and solve the sunspot analyzing algorithm.
Tra lai em niem vui khi duoc gan ben em, tra lai em loi yeu thuong em dem, tra
lai em niem tin thang nam qua ta dap xay. Gio day chi la nhung ky niem buon...
http://nhatquanglan1.0catch.com
-
Stay in the project, SunspotsAnalyzer.
-
Create a new class, SunspotAnalyzer (a subclass of ParallelAlgorithm), using
the following declarati on:
class SunspotAnalyzer : ParallelAlgorithm
-
Add the following lines of code at the beginning, as we are going to use the
System.Drawing classes):
using System.Drawing;
-
Add the following private variables (the original bitmap and the resulti ng
one, the bitmaps list, and the total number of pieces or threads):
// The Bitmap
private Bitmap proOriginalBitmap;
// The resulting bitmap
private Bitmap proBitmap;
// The bitmaps list
private List<Bitmap> prloBitmapList;
// The total number of pieces
private int priTotalPieces;
-
Add the following properti es to access the private variables (we want to
create a compact and reliable class):
public Bitmap poBitmap
{
get
{
return proBitmap;
}
}
public int piTotalPieces
{
get
{
return priTotalPieces;
}
}
-
Add a constructor with a parameter (the bitmap to analyze or invert):
public SunspotAnalyzer(Bitmap proBitmap)
{
proOriginalBitmap = proBitmap;
// Create threads taking into account the number of lines in
the bitmap and the number of available cores
priTotalPieces = Environment.ProcessorCount;
CreateThreads(proOriginalBitmap.Height, priTotalPieces);
CreateBitmapParts();
}
-
Add the following functi on, CropBitmap. It will crop the bitmap received as a
parameter and return the portion of the original defi ned by the Rectangle
proRectangle:
private Bitmap CropBitmap(Bitmap proBitmap, Rectangle proRectangle)
{
// Create a new bitmap copying the portion of the original defined by
proRectangle and keeping its PixelFormat
Bitmap loCroppedBitmap = proBitmap.Clone(proRectangle, proBitmap.PixelFormat);
// Return the cropped bitmap
return loCroppedBitmap;
}
-
Add the following procedure, CreateBitmapParts. It will assign the bitmap part
corresponding to each piece:
private void CreateBitmapParts()
{
// Create the bitmap list
prloBitmapList = new List(priTotalPieces);
int liPieceNumber;
Bitmap loBitmap; for
(liPieceNumber = 0; liPieceNumber < priTotalPieces;
liPieceNumber++)
{ loBitmap
= CropBitmap(proOriginalBitmap, new Rectangle(0, (int)
prloPieces
[liPieceNumber].piBegin, proOriginalBitmap.Width, (int)
(prloPieces [liPieceNumber].piEnd -
prloPieces[liPieceNumber].piBegin +
1))); prloBitmapList.Add(loBitmap);
// Assign the bitmap part corresponding to the
piece
((SunspotAnalyzerPiece)prloPieces[liPieceNumber]).poBitmap =
loBitmap; } }
What just happened?
We added the necessary variables, properti es, and methods strictly related to a
bitmap algorithm. We could have created a BitmapParallelAlgorithm class instead
of directly working on the SunspotAnalyzer class.
The code to work with specifi c Bitmap pieces is now held in the new
SunspotAnalyzer class (a subclass of ParallelAlgorithm). The instance of this
class will consti tute a very simple-to-use algorithm. We must override
everything we need to complete the methods, as the algorithm coordinati on
resides in the instance of this class.
Now we have completed the skeleton for the piece of an algorithm defi ned as a
subclass of the ParallelAlgorithmPiece class.
Creating simple constructors
We created a very simple constructor that receives a Bitmap as a parameter:
public SunspotAnalyzer(Bitmap proBitmap)
It saves the original bitmap in the proOriginalBitmap private variable and
creates the threads taking into account the number of lines in that bitmap and
the number of available cores. However, this is accomplished by calling the
CreateThreads method, defi ned in the ParallelAlgorithm superclass, using these
simple lines:
priTotalPieces = Environment.ProcessorCount;
CreateThreads(proOriginalBitmap.Height, priTotalPieces);
It also saves the total number of pieces in the priTotalPieces private variable
because we will need them later.
Then, it calls the specifi c CreateBitmapParts method. Again, we could have
created a BitmapParallelAlgorithm class, but we must simplify the example.
The constructor leaves everything prepared to start running the threads without
any additi onal method calls.
We already know the CreateBitmapParts method and the CropBitmap functi on. They
work the same way they did in the previous examples. However, in this case, the
CreateBitmapParts method takes into account the piBegin and piEnd properti es
for each piece defi ned, as shown in the following lines of code in the loop:
loBitmap = CropBitmap(proOriginalBitmap, new Rectangle(0,
(int) prloPieces[liPieceNumber].piBegin,
proOriginalBitmap.Width,
(int) (prloPieces[liPieceNumber].piEnd -
prloPieces[liPieceNumber].piBegin + 1)));
prloBitmapList.Add(loBitmap);
((SunspotAnalyzerPiece)prloPieces[liPieceNumber]).poBitmap =
loBitmap;
In the last line, it casts prloPieces[liPieceNumber] to SunspotAnalyzerPiece
because it must access its poBitmap property, which is exclusive of the
subclass. It assigns the bitmap part corresponding to that piece.
We have created the algorithm coordinati on subclass, and added the variables,
properti es, and methods needed for the sunspot analyzer algorithm.
Now, we are going to fi ll in the gaps overriding the necessary methods to defi
ne the code for creati ng and coordinati ng the pieces. Thus, we are going to
represent the complete sunspot analyzer parallel algorithm:
-
Stay in the project, SunspotsAnalyzer.
-
Move to the SunspotAnalyzer : ParallelAlgorithm class code area.
-
Override the StartThreadsAsync method to force a garbage collecti on
before starti ng the threads with an asynchronous executi on:
public override void StartThreadsAsync()
{
// Call the garbage collector before starting each thread
ForceGarbageCollection();
// Run the base code
base.StartThreadsAsync();
}
-
Override the CreateParallelAlgorithmPiece method with the code that is going to
create the specifi c piece instance:
public override ParallelAlgorithmPiece
CreateParallelAlgorithmPiece(int priThreadNumber)
{
return (new SunspotAnalyzerPiece(priThreadNumber));
}
-
Override the CollectResults method with the code that is going to join the
pieces (in this case, the bitmaps):
public override void CollectResults()
{
// Enter the results collection iteration through the results left in each
ParallelAlgorithmPiece
int liPieceNumber;
// Each bitmap portion
Bitmap loBitmap;
// Create a new bitmap with the whole width and height
loBitmap = new Bitmap(proOriginalBitmap.Width, proOriginalBitmap.Height);
Graphics g = Graphics.FromImage((Image)loBitmap);
g.InterpolationMode = System.Drawing.Drawing2D.
InterpolationMode.HighQualityBicubic;
for (liPieceNumber = 0; liPieceNumber < priTotalPieces; liPieceNumber++)
{
// Draw each portion in its corresponding absolute starting row
g.DrawImage(prloBitmapList[liPieceNumber], 0,
prloPieces[liPieceNumber].piBegin);
}
// Assign the generated bitmap to proBitmap
proBitmap = loBitmap;
g.Dispose();
}
What just happened?
The code required to implement the sunspot analyzer algorithm is now held in the
SunspotAnalyzerPiece and SunspotAnalyzer classes.
Thus, creati ng an instance of the SunspotAnalyzer class, we can easily invert
any bitmap using as many threads as the number of available cores. We have fi
lled in the gaps and have overridden some methods focusing on the algorithm
problems instead of working hard on splitti ng techniques.
Look at the code in the two subclasses. It is very easy to understand, and is
also very well encapsulated.
We added a call to the ForceGarbageCollection method in the overridden
StartThreadsAsync procedure. As we learned in the previous cases, forcing the
garbage collector before starti ng the threads is a good practi ce. Then, we
called the base code, because the behavior is then the same as in the
superclass. The following line of code does that:
base.StartThreadsAsync();
We had to override the CreateParallelAlgorithmPiece method. This method will
create the appropriate instance of a ParallelAlgorithmPiece subclass:
public override ParallelAlgorithmPiece CreateParallelAlgorithmPiece(int
priThreadNumber)
{
return (new
SunspotAnalyzerPiece(priThreadNumber));
}
It returns a new SunspotAnalyzerPiece instance, sending the thread or piece
number as a parameter.
Overriding this method, we can return instances of the SunspotAnalyzerPiece
class (a subclass of ParallelAlgorithmPiece).
Programming the results collection method
We also had to override the CollectResults method. This method will collect the
results left in each SunspotAnalyzerPiece subclass instance.
We already know the mechanism, since it is the same that we used in the previous
examples. However, in this case, the CollectResults method takes into account
the piBegin property for each piece defi ned, as shown in the following lines
of code in the loop:
for (liPieceNumber = 0; liPieceNumber < priTotalPieces; liPieceNumber++)
{
g.DrawImage(prloBitmapList[liPieceNumber], 0,
prloPieces[liPieceNumber].piBegin);
}
Then, it assigns the generated bitmap to proBitmap, which can be accessed from
the outside world by the poBitmap property:
proBitmap = loBitmap;
Do not forget the NASA scienti sts and NASA's CIO. They are excited about your
new applicati on, and you do not want to disappoint them. You would rather work
for the NASA and not the FBI or the hackers!
As they want to run the algorithm for a lot of images, fi rst, we are going to
create a new method for processing an individual bitmap. This way, we will be
able to call it many ti mes for each bitmap found in a folder:
-
Stay in the project, SunspotsAnalyzer.
-
Add the following procedure in the form's fi le, AnalyzeSunspotsAndShowResult.
It will create a SunspotAnalyzer instance, run the algorithm, and show the
resulti ng bitmap in a picturebox control. It receives a Bitmap as a parameter:
private void AnalyzeSunspotsAndShowResult(Bitmap proBitmap)
{
SunspotAnalyzer loSunSpotAnalyzer;
loSunSpotAnalyzer = new
SunspotAnalyzer(proBitmap);
loSunSpotAnalyzer.RunInParallelSync();
loSunSpotAnalyzer.CollectResults();
picSunspots.Image = loSunSpotAnalyzer.poBitmap;
}
What just happened?
The code required to analyze sunspots (inverti ng the bitmap colors) and show
the resulti ng bitmap in the picturebox control is now held in a procedure
already prepared for creati ng as many threads as the number of available cores
with just a few lines of code. A Bitmap is received as a parameter for this
method.
Forgetting about threads
Look at the code! There is nothing about threads, just a few lines creati ng an
instance and calling two methods. The magic of object-orientati on allows us to
simplify several threading issues and reduce the parallelism complexity. The
developer who writes this code could be a classic C# user interface or events
programmer. He or she does not have to worry about multi ple threads, cores,
processors, and so on. Everything is encapsulated in the parallel
algorithm-specifi c subclass.
First, it creates a SunspotAnalyzer instance, passing the enti re bitmap
received as a parameter to the constructor.
loSunSpotAnalyzer = new SunspotAnalyzer(proBitmap);
This call creates the pieces and gets everything prepared for an asynchronous
executi on. However, the method wants to return when everything is fi nished.
Therefore, it calls the RunInParallelSync method:
loSunSpotAnalyzer.RunInParallelSync();
The threads are executed asynchronously. However, the RunInParallelSync method
does not return unti l all the threads are fi nished with their work.
Then, it collects the results, leaving the processed bitmap accessible in the
poBitmap property. Hence, it assigns it to the picSunspots picturebox:
loSunSpotAnalyzer.CollectResults();
picSunspots.Image = loSunSpotAnalyzer.poBitmap;
Now, we are going to create the UI and write some code to use the
SunspotAnalyzer class and its encapsulated power:
-
Stay in the project, SunspotsAnalyzer.
-
Open the Windows Form Form1 (frmSunspotsAnalyzer) in the form designer, add the
following controls, and align them as shown in the image:
One picturebox (picSunspots) with its SizeMode property set to StretchImage.
One butt on showing a space shutt le and its Text property set to Run sunspots
analyzer batch (butRunBatch). This butt on will start an instance of the
parallel algorithm subclass, calling the AnalyzeSunspotsAndShowResult method
for each image fi le found in a folder.
-
Add the following lines of code at the beginning (as we are going to use the
System.IO.DirectoryInfo and System.IO. FileInfo classes):
using System.IO;
-
Open the Click event in the butt on butRunBatch, and enter the following code
(replace C:\\NASASUNSPOT with your images folder path):
DirectoryInfo loDirectory;
FileInfo[] loImageFiles;
Bitmap loBitmap;
// Replace “C:\\NASASUNSPOT” with your images folder path
loDirectory = new DirectoryInfo(“C:\\NASASUNSPOT”);
// Get the JPG files stored in the path
loImageFiles = loDirectory.GetFiles(“*.JPG”);
// Process each JPG image file found
foreach (FileInfo loNextImageFile in loImageFiles)
{
// Store the Bitmap to be processed in the proOriginalBitmap private variable
loBitmap = new Bitmap(Image.FromFile(loNextImageFile. FullName));
AnalyzeSunspotsAndShowResult(loBitmap);
// Let the PictureBox control update the image
Application.DoEvents();
}
-
Copy the images to be analyzed in the specifi ed folder (the default is
C:\\NASASUNSPOT). You will need more than 10 images with high resoluti ons to
see the progress.
-
Build and run the applicati on.
-
Click on the Run sunspots analyzer batch butt on. The images will be shown with
their colors inverted, one aft er the other, with a delay (depending on the
parallel processing capabiliti es of the computer) of a few seconds, as shown
in the following image:
What just happened?
The NASA scienti sts and its CIO are very happy with your batch sunspot
analyzer, and they are going to run it in the main server with 3,000 huge
resoluti on images. You do not need to keep your fi ngers crossed, as the
classes forced the garbage collector to run when needed. You are hired!
However, you will need a more intensive training in avoiding some multi
threading problems.
We could easily transform a new complex multi threaded algorithm capable of
taking full advantage of parallel processing capabiliti es into subclasses of
the generalized ParallelAlgorithmPiece and ParallelAlgorithm abstract
superclasses. We can create instances of the subclass that represent the
algorithm every ti me we need them, without worrying about threads and
available cores thanks to object-oriented capabiliti es in the C# programming
language.
Optimizing and encapsulating parallel algorithms
Look at the code in the fi nal applicati on! Using an object-oriented
generalizati on process, we dramati cally simplifi ed the parallel algorithm
creati on process.
Now, we could create a parallelized bitmap processing algorithm by creati ng
subclasses and making minor changes to the code.
Generalizing a parallel algorithm allows us to focus on the algorithm itself,
and later we can easily opti mize the algorithm making some changes to the
methods.
Achieving thread affinity
Using and improving the classes and the encapsulati on capabiliti es off ered by
the C# programming language, and accompanied by a good design, we can achieve
the concept known as thread or resource affi nity. We have been working on that
without talking about it—someti mes you must run before you can walk!
Thread affi nity promotes a task-oriented programming. We worked hard on splitti
ng a job into highly independent tasks. Each task works in an independent
thread with its instance variables and its local variables. It does not change
the states of variables visible to other threads.
We worked on asking states or collecti ng states, but not changing them, except
in the case of some very simple fl ags to interrupt the processing.
In real-life applicati ons, it is nearly impossible to hide all the state
changes by one thread from the other threads. It is possible, but it requires
developing a very complex and also a processing-ti me-consuming framework. The
performance improvements we achieve with multi threading could be lost with the
instructi ons required to implement a very effi cient affi nity.
There are many techniques required to achieve thread affi nity. Their usage
depends on the kind of applicati on we are developing. If we are working on a
new multi threaded kernel, on a complex middleware, or on a low-level service,
we will have to be very careful about the states, the affi nity, and the
problemati c synchronizati on directi ves and objects.
To ensure thread affi nity, we must not allow any foreign thread to make changes
in the variables in which our threads are working, except some simple fl ags to
control its behavior.
Avoiding locks and many synchronization nightmares
So far, we have been working with parallelized algorithms and we've used C#
object-oriented capabiliti es to simplify the code and make it easier to
understand. We have specialized classes using inheritance and many encapsulati
on techniques to simplify the parallel algorithm creati on process. Hence, we
avoided repeati ng the same code on every new applicati on.
However, we have been avoiding some common nightmares related to multi threaded
programming– locks and synchronizati on problems. How can we use C# and .NET
capabiliti es to synchronize access to variables shared by several diff erent
threads?
The answer is very simple, avoiding locks and dodging synchronizati on problems.
The best way to simplify these common concurrency problems is to avoid them as
much as possible, because they are always big nightmares for the developers.
The best way to understand synchronizati on problems in multi threaded applicati
ons is to compare them with the concurrency problems found in databases used by
many users at the same ti me.
If you need to work on the same table's records (rows), you have to be very
careful with the updates. There are many approaches, corresponding to many diff
erent situati ons. Someti mes, you must lock the register unti l you are fi
nished with writi ng the changes. Why? Because many users may be reading and
writi ng concurrently to those records by using a diff erent computer connected
to the same database. The same happens with multi ple threads in modern multi
processing capable computers; many threads can change values at the same ti me.
This is indeed very dangerous. We must be very careful when working with
databases used by many users.
There are many synchronizati on classes, methods, and structures off ered by C#
and .NET. They allow us to block one or many threads unti l a certain conditi
on is reached in another thread. They provide locking mechanisms to avoid
changing the value of a variable in many threads simultaneously. However, our
approach is to avoid using them as much as possible. This way, we can keep our
code simpler, and can avoid many diffi cult-to-solve bugs related to the
synchronizati on mechanism.
Summary
We have learned a lot in this chapter about using object-oriented capabiliti es
off ered by the C# programming language, using design patt erns for simplifying
the parallelism complexity, and avoiding synchronizati on pains. Specifi cally,
we covered:
-
Using the factory method class creati onal patt ern to create classes prepared
for inheritance, and hence simplifying the creati on of parallelized code
-
Designing effi cient and pragmati c object-oriented multi threaded code
-
Creati ng instances and calling some methods in single-threaded code to create
encapsulated and well-managed multi threaded code
-
Encapsulati ng multi threaded algorithms in classes to simplify the development
process and to allow a division of the development team in single-threaded and
multi threaded code
-
Creati ng safer, independent pieces of work fi lling the gaps in inherited
classes
-
Specializing segmentati on algorithms, while avoiding synchronizati on pains
and achieving thread affinity
We also learned the principles of thread affi nity, and how to avoid the
undesirable sideeff ects related to concurrent programming.
Also see
Define assembly.
What is a Constructor?
What is a Destructor?
Define abstract class in C#.NET.
Define serialization.
C#.Net support multiple inheritance, comment.
Can private virtual methods be overridden in C#.NET?
Is is possible to force garbage collector to run?
What is the role of data provider?
Describe how a .Net application is compiled and executed.
What is an Event?
Define Delegate.
What is Assembly manifest?
What is GAC (global assembly cache)?
How do we access crystal reports in .NET?
What are the various components in crystal reports?
What basic steps are needed to display a simple report in crystal?..........
.Net
Framework
This includes introduction of .Net framework, .Net framework architecture, role
of assembly and GAC.
.NET
Code Security
This includes explanation of code security, Principal object, declarative and
imperative security, role-based security, code access security and code group.
|