|
RiverSoftAVG Newsletter #2
December 2000/January 2001
Hello and welcome again to the RiverSoftAVG New
Millennium newsletter! It's hard to believe the new year and the real
new millennium is almost here. We are gearing up to develop the next
major release of the Inference Engine Component Suite, please take the time
to fill out the questionnaire at the bottom of the newsletter. Please
also be aware that RiverSoftAVG will be on vacation the last week of
December and the first week of January. Happy Holidays and a Happy New
Year!
Contents
Note: You received this newsletter because
you are an owner of a RiverSoftAVG product. If you received this
newsletter by mistake or if for any reason you wish to not receive any
future mailings from RiverSoftAVG, just reply to this message informing us
so. We apologize for any intrusion.
The Inference Engine Component Suite has been
upgraded! On December 9th, we released version 1.1 of the IECS.
The upgrade adds the following features:
This is a free upgrade so if you haven't gotten
it yet, rush and get it at http://www.riversoftavg.com/patches.htm
The complete version history is available at http://www.riversoftavg.com/version_history.htm
All the RiverSoftAVG newsletters are now available online (the whole two of them :) ). Hopefully, this will become a repository of useful information. Go to http://www.RiverSoftAVG.com/articles_&_tips.htm
You may have noticed that RiverSoftAVG made the
transition in late November to a new web host. It was NOT a smooth
transition for which we apologize profusely. FrontPage is a quirky
beast (gee, what a surprise!) and the only way to get the extensions to work
properly was to modify pages online. Everything is stable now and the
web site should be better than ever. The site is faster and we have
added the capability to search the site.
The Inference Engine architecture provides
rudimentary help for running your inference engines in threads besides
your main one. Perhaps you were not aware of it, but the
TInferenceEngine component has Lock and Unlock methods. You can use
these methods to tell the inference engine to temporarily stop or block (while
you change its properties or call other methods) and then continue, like
so:
InferenceEngine1.Lock;
try
// your code here
finally
InferenceEngine1.Unlock;
end;
The lock and unlock methods use a critical
section (TCriticalSection) to temporarily block another thread. The
inference engine's run method calls the lock and unlock every step to
ensure you don't stomp on any inference engine structures (such as the
Agenda) while it is inferring new facts. Of course, this assumes
that you call the Lock and Unlock methods as well.
For this example, we are going to make a
simple appiclation that executes two TInferenceEngine components in
auxiliary threads, leaving the main application thread free. The
source code and the expert systems are available from the web site at www.RiverSoftAVG.com/ThreadDemo.zip
The expert systems I use in this example are fullmab.ie and wordgame.ie.
These two expert systems are ideal because they take more than a couple of
seconds to finish so we can see them executing. Note that if you
are compiling the thread demo from scratch and you only have the demo
version of the Inference Engine Component Suite, the wordgame.ie will
not work because it violates the demo limits. You will receive Out
Of Memory exceptions. You can, however, modify the demo to load
another expert system instead.
Setting Up The Main Form
First, let's set up the main application.
Create a new application and drop two TInferenceEngine threads on the
form. Drop two TListBox components on the form (to hold the output
from the inference engines, you can use a TMemo component as well).
Finally, drop a TButton on the form, this button will be used to start or
restart the threads.
To run the threads, create a OnClick event
handler for the TButton. In here, we will lock the inference
engines, load our expert systems and resume the threads if they are
stopped. The code below does just that:
procedure
TForm1.Button1Click(Sender: TObject);
begin ListBox1.Items.Clear; ListBox2.Items.Clear; // Lock Inference Engine1 and add the .IE file with InferenceEngine1 do begin // Lock the InferenceEngine so that we can modify it without stomping // on the other thread Lock; try // Tell the engine to stop when we wake it back up Halt := True; // Clear the rules, facts, and everything else Clear; LoadFromFile( 'fullmab.clp' ); // reset the engine to prepare for inference Reset; finally Unlock; end; end; // Lock Inference Engine2 and add the .IE file with InferenceEngine2 do begin // Lock the InferenceEngine so that we can modify it without stomping // on the other thread Lock; try // Tell the engine to stop when we wake it back up Halt := True; // Clear the rules, facts, and everything else Clear; LoadFromFile( 'wordgame.clp' ); // reset the engine to prepare for inference Reset; finally Unlock; end; end; if Thread1.Suspended then Thread1.Resume; if Thread2.Suspended then Thread2.Resume; end;
To save memory, we are going to use another
great feature of the Inference Engine Component Suite and share the user
functions between the two TInferenceEngine components. Shift-click
the two components and empty out the Packages property set. Now drop
on the form the TUserPackages we might need, I dropped the
TStandardPackage, TPredicatePackage, TStringPackage, TMathPackage, and
TMiscPackage. Do NOT set the Engine properties of the user packages,
we will set that in the form OnCreate event.
A word of warning... sharing packages is a
great feature but be careful of any user functions that store data in the
object. The two inference engines could stomp on each others' data.
For example, the TPrintOutFunction stores the last text output (to make it
available as a prompt for the read functions). I wanted to keep the
example simple so the two expert systems we are using don't require user
input. This makes sharing every function safe in this case
but you do want to be aware of it.
Creating Our Thread object
So, to run an inference engine in another
thread, we need to make a TThread descendant. This thread will be
responsible for executing the TInferenceEngine you supply it until it is
terminated. Create a new thread object using File->New... Thread
Object. I named the thread TIEThread.
For this thread, we want to pass to the
constructor the TInferenceEngine component to run AND the TListBox to put
the output. First, we create two private fields of the object to
hold the inference engine and the list box. Here is our constructor
so far:
constructor
TIEThread.Create(IE: TInferenceEngine; ListBox: TListBox);
begin // Create thread suspended inherited Create( True ); // IE is the inference engine to run FIE := IE; end;
Of course, the Execute method is extremely
simple:
procedure TIEThread.Execute;
begin while (not Terminated) do IE.Run; end;
To send output to the list box, we need to
create an OnPrintOut event handler in the thread and assign it to the
inference engine:
procedure
TIEThread.InferenceEnginePrintOut(Sender: TObject; OutID,
Text: String); begin ListBox.Items.Add( Text );
end;
Modify the constructor for the assign:
constructor TIEThread.Create(IE:
TInferenceEngine; ListBox: TListBox);
begin // Create thread suspended inherited Create( True ); // IE is the inference engine to run FIE := IE; IE.OnPrintOut := InferenceEnginePrintOut; end;
BUT WAIT A SECOND!!! We should not
directly access VCL object methods and properties from this thread.
In fact, when we create the thread, Borland even includes a caution to
wrap all access in Synchronize calls. Ok, we can change the
InferenceEngineOnPrintOut to call synchronized another thread method which
updates the Listbox. That would work, right? Wrong.
Unfortunately, this simple fix will not work in the case. To see
why, look at our Button1OnClick event handler. To protect the
inference engine, the method locks it, changes it, and then unlocks it.
However, if the inference engine is already locked, the Lock method call
will block the calling thread (in this case, the main thread). If
you remember, I also told you the Run method calls the Lock and Unlock
method whe executing a step. If the inference engine has locked
itself to execute a step and that step calls the PrintOut method, the
TIEThread object will try to synchronize (blocking itself) with the main
thread. Deadlock! Uh oh.
So now what do we do? The main problem
is the printout call. If we were not trying to synchronize with the
main thread, no problem. If we put on our thinking caps, we can come
up with a solution (probably more than one). The solution I came up
with is to add a message passing thread between the TIEThread and the main
thread. The TIEThread object will never actually interact with the
main thread. Instead, it will push a message onto the communications
thread's queue and immediately return. The communications thread
will periodically check its queue, and, when it finds a message, pop it
off, synchronize with the main thread, and modify the listbox. The
secret is that the message queue will use one critical section and the
Listbox modification will use the main thread's synchronization. The
communication thread will release the pop the queue and release the
critical section (allowing the TIEThread object to grab the critical
section and push messages onto the queue) BEFORE synchronizing with the
main thread.
In the interests of brevity (this tip is
already WAY too long), I won't list the TIECommThread source. Please
get the source code from the web site. To summarize, the
TIECommThread will allocate a TStringList (as our queue), a critical
section (to protect access to the queue), and methods to push and pop.
The TIEThread object is changed to create and start a TIECommThread (as
well as terminate it), and the OnPrintOut method calls the
TIECommThread.Push method.
constructor
TIEThread.Create(IE: TInferenceEngine; ListBox: TListBox);
begin // Create thread suspended inherited Create( True ); Priority := tpLower; // create the communications thread FCommThread := TIECommThread.Create( ListBox ); FCommThread.FreeOnTerminate := True; // IE is the inference engine to run FIE := IE; IE.OnPrintOut := InferenceEnginePrintOut; end;
destructor TIEThread.Destroy;
begin CommThread.Terminate; inherited; end;
procedure
TIEThread.InferenceEnginePrintOut(Sender: TObject; OutID,
Text: String); begin // pass the message to the communication thread CommThread.Push( Text ); end;
Wrapping up
Ok, what is next? Way back at the
beginning of the tip, I promised you we would add the TUserPackages to the
inference engines on form construction. That is exactly what I am
doing here. We also need to allocate the two TInferenceEngine
threads. Declare two fields, Thread1 and Thread2, of type TIEThread.
We will create them in the constructor:
procedure
TForm1.FormCreate(Sender: TObject);
begin // Add the packages we need to both inference engines, // sharing resources and saving memory. Note, in a real application // you would NOT want TPrintOutFunction and TReadXXXFunction to share // since they access one variable. We are only going to run non-input // expert systems so we will be ok StandardPackage1.Reasoners.Add( InferenceEngine1 ); StandardPackage1.Reasoners.Add( InferenceEngine2 ); MathPackage1.Reasoners.Add( InferenceEngine1 ); MathPackage1.Reasoners.Add( InferenceEngine2 ); PredicatePackage1.Reasoners.Add( InferenceEngine1 ); PredicatePackage1.Reasoners.Add( InferenceEngine2 ); StringPackage1.Reasoners.Add( InferenceEngine1 ); StringPackage1.Reasoners.Add( InferenceEngine2 ); MiscPackage1.Reasoners.Add( InferenceEngine1 ); MiscPackage1.Reasoners.Add( InferenceEngine2 ); // Create a thread for each inference engine Thread1 := TIEThread.Create( InferenceEngine1, ListBox1 ); Thread2 := TIEThread.Create( InferenceEngine2, ListBox2 ); end;
Finally, we need terminate the threads.
We will put the terminate code in the OnDestroy event:
procedure
TForm1.FormClose(Sender: TObject; var Action:
TCloseAction);
begin Thread1.Terminate; Thread2.Terminate; // wait for a couple seconds so that the threads can terminate, // with a real application, you would probably want to wait on 2 variables // that would be set in the thread's OnTerminate event Sleep(2000); end;
Conclusion
Making the inference engine component suite
work with multiple threads is certainly possible. However, before
starting the work of doing so, perhaps you should ask yourself whether you
need to. Making a multi-threaded program can be a big headache,
especially to debug. Instead, consider using the
TInferenceEngine.Run( 1 ) command. This method allows you to
tell the inference engine to execute one step only before returning.
Besides allowing you to avoid threading, you also avoid thread balancing
issues - starving some threads at the expense of others. You
can call the Run(1) method for multiple inference engines and ensure each
gets an equal amount of CPU power.
Please keep in mind that this article is meant
as a starting point for developing your own applications using the IECE in
separate threads. The example has been simplified to make it as
short as it can be. The threads do NOT handle exceptions, which
would definitely cause problems if they occured (the two IE files in this
example don't though). You also would want to consider using only
one communication thread to avoid bogging the system down with too many
threads.
I hope this article/tip has been helpful.
Any questions or comments, please email me at
tggrubbNO@SPAMRiverSoftAVG.com
We are starting the work on the next upgrade for
the IECS. We need to know how we are doing and what you would like to
see. Please take the time to fill out the survey below.
Inference Engine Component
Suite Survey
December, 2000 1) How much time have you spent working with the Inference Engine Component Suite? a) More than an hour a week b) Hour a week c) Less than an hour a week d) Haven’t installed it 2) What is your favorite feature of the Inference Engine Component Suite? ____________________________________________________________________________________ 3) What is your least favorite feature of the Inference Engine Component Suite? ____________________________________________________________________________________ 4) What feature is missing from the suite that you would most like to see? a) Database Support b) Better editors c) Better demos d) Component Setup wizard e) Fuzzy Logic f) Other, specify __________________________ 5) What is the best (most useful, most inspiring) demo included with the suite? a) Advanced Console Demo b) Basic Console Demo c) Paint Scripting Demo d) Personnel Evaluation Demo 6) What kind of demo would you like to see that is not included? ____________________________________________________________________________________ 7) Would you buy, or recommend to buy, the Inference Engine Component Suite? a) Yes b) No c) Maybe 8) Other comments ____________________________________________________________________________________ ____________________________________________________________________________________ ____________________________________________________________________________________ ____________________________________________________________________________________
|
Send mail to
webmasterNO@SPAMRiverSoftAVG.com with questions or comments about this web
site.
|