|
Handling Threads
DEMONSTRATIVE EXAMPLES
======================
EXAMPLES ON THREADS
===================
Working with threads is normally very hard, but with PC#, you
can create as many threads as you like and write 100% thread safe code with minimum worry. How? The unique PC# programming
style has made it possible. Assuming that you have already looked at most of the "PC# Demonstrative Examples", you must
have noticed the following:
(1) You rarely need to define new variables in your program. The few
variables which are used for communication between your program and PC# methods are mostly all you need.
(2) All PC# methods are uniform. All methods expect one string (the
mode) as the only parameter and all return no value (void). Also all method names are made of one
unique character followed with the method type indicator 'm'.
SOURCES OF DATA CORRUPTION:
(1) Use of common variables: When two threads access a common
variable, there is always the possibility of data corruption. The reason is simple. If "thread 1" wanted
to calculate (y=x+1) where x=5, it may do it in two steps as follows: step 1:x=5;
Step 2: y=x+1; Now, immediately after the first step has been executed, if "thread 2" changes the
value of (x) to 0, the result "thread 1" will get is going to be be 1 instead of the expected value
of 6.
(2) Use of common methods: For similar reason we should expect
data corruption to happen if two threads access the same method at the same time.
(3) Accessing common resource: Imagine if a bank program uses
different threads to serve different customers and a man and his wife access the same account at the
same time. The man wanted to withdraw $100, so his thread checked his balance and found $100
there so the transaction was flagged "acceptable". His wife immediately did the same, so her thread
also checked the same account's balance and found the same $100 there. So her transaction was also flagged
"accetable". Both received their cash and their bank ran out of cash.
HOW ARE THREADS CREATED:
PC# identifies threads by their serial number. Whenever you
like to create a new thread you call xm("tc") supplying it with the number you like to identify the thread with. The thread
created will be named with the number you have supplied. When the thread starts running, the first thing it does is to
declare a new variable (t) of type "int" to store its number into, then it analizes its name to know its number and stores
it into (t)
As you can see, (t) is unique for each thread, so it's thread safe
and is used for communicating with the thread safe method mm() which will be discussed later.
Thread number zero is the main thread which your program starts at
and where all other threads are created.
When C# creates a new thread, it requires supplying it with the name
of the method which the thread will execute when it starts. In PC# all threads excecute method run() when they start.
So, it becomes a question of which block number to start at within method run() Your program supplies xm("tc") also with
the block number which the thread should start at assigned to (bli)
Multi-threads programs consume plenty of system resources, so you
must be careful. By default, the highest number of threads to use is 10. This includes the main thread. However, you
can increase or reduce this number using method dm("ht") as you'll see in the coming examples.
HOW COULD PC# OVERCOME THE DATA CORRUPTION PROBLEMS?
(1) Use of common variables: Fortunately, The variables which
are used by your program to communicate with PC# methods are limited in number. So PC# has created special
arrays called "variable groups" allowing each thread to have its own private variable of
each kind. for example group osg[] is a grpup of (os)'s. Each thread has its own copy of the variable
(os) stored at row osg[t] where (t) is the thread number.
So, when you write a multi-thread program and like
to display the string "Hello World", you replace the line of code [os="Hellow World";tm("dl");] with
the thread safe one [osg[t]="Hello World";mm(t,"tdl");]. Method mm() will be explaind next. All
you need to know now is that since each thread has its own thread number (t), osg[t] is unique
for each thread, so data corruption caused by accessing common variables is impossible to happen.
The group names are made by adding the lower case
char 'g' to the name of the variable if it was a single value variable, and by adding the upper case
char 'G' if it was a multi-value varriable like an array. Here is a table to show you how to form the
thread safe variable names:
REGULAR: i
ls lf id ib
cls dnb blp OS THREAD-SAFE:
ig[t] lsg[t] lfg[t] idg[t] ibg[t] clsg[t] dnbg[t] blpg[t] OSG[t]
Each variable which is used for input or output
of any PC# methods has its thread-safe group name. The only exception is (bli) "requested block number"
which we need only one copy of. This is because all threads other than the main thread are confined
to one single block. They are not allowed to jump between blocks so they don't need a thread-
safe (bli). However, there is a thread safe group for (blp) "present block number" which is (blpg)
(2) Use of common methods: In order to prevent data corruption
which hapens when more than one thread access the same method, programmers use the "lock(this)" directive,
to allow only one thread to access the method at one time. Although this is necessary when you work
with multiple threads, it slows down program execution, so it must be eliminated whenever you write
a single thread program.
All PC# methods are not thread safe. They work
efficiently with single thread programs, but they cannot handle multi-thread ones. So, how can we handle
multiple threads while keeping them the same for single thread use? The answer is the "Universal Method
mm()".
Method mm() supplies a thread safe access to all
other PC# methods. When a thread likes to call any PC# method through the thread-safe method mm(), it
supplies it with two items, its thread number (t) and the mode string. The mode string is made by
concatenating the first char of the method name with the mode required. Let us have an example:
To call tm("dl") in a thread safe manner call mm(t,"tdl")
(t) is the current thread number and "tdl" is made
of two parts, "t" means call method tm() and "dl" is the mode to call tm() at.
In order to insure complete thread safety, method mm() uses the thread-safe group variables discussed
above for both input and output. In order to give you a clear picture, let us see what goes inside method
mm() when it executes the code:
osg[t]="Hello World";mm(t,"tdl");
public void mm(int t,string md) {
lock(this) {
// Lock the object. To serve one thread at a time os=osg[t];
// Convert all input values from private to common tm();
// Call wanted method osg[t]=os;
// Convert all output values from common to private Monitor.Pulse(this);
// Wake-up the older thread which has been waiting Monitor.Wait(this,10);
// Wait 10 milliseconds to allow other threads
// a fair share. }
// Unlock the object }
// Return to calling program
(3) Accessing common resources: Now you must agree that accessing
method mm() is 100% thread safe, but how can we overcome the problem arising from accessing a common
resource like a "file" as we have discussed before?
The answer is to keep the current object locked
while a full block of code or a full method is being executed. Using the same example which has been
used before, if the banking program has done so, it could have kept the wife from accessing the withdrawl
method until her husband's transaction has been completed and the balance has been updated. This could
have eliminated the error.
Of course, you can do so by yourself, however PC#
likes to keep your program easy, simple and error free, so it has developed a mechanism to do this for
you.
All you need to do, is to write a method containing
the code which must be executed in full by one thread while others are blocked and name this method
one of the 10 pre-defined method names "m0(), m1(),...or m9()". Then access this method through
the universal thread-safe method mm() which will do the object locking for you.
Fortunately, the method should be written as if
it was made for single thread operation. Within the method, you use common variables and call any PC#
method directly. So you get thread safety without working for it.
Methods m0():m9() can require either one or two
parameters when called. For example you can write method m0() in two different formats:
public override m0(int t) {.....}
or: public override m0(int t,string md) {.....}
The "int" variable expects the thread number and
the string variable expects the mode.
We'll see some examples on using these methods
later. ========================================================================================= Example 1: This program
will create 7 threads (threads 1:7) in addition to the main thread (thread 0) Each thread will display a message in a unique
color telling us its number ========================================================================================= public
class a:pcs {
public override void init() { tia=toa="t"; bli=0;
base.init(); } public override void run() { //---- The following three lines must
be included into any multi-thread program ----- int t;
// Thread # Defined here so each thread thp=Thread.CurrentThread;
// will have its own unique copy. t=Int32.Parse(thp.Name);
// Value of (t) for current thread //-----------------------------------------------------------------------------------
if (blpg[t]==0) { os="Hello, this is thread number zero, I'll be creating 7 threads.";tm();
os="Each thread will be introducing itself twice.";tm();
// No thread safety worry until now. for (c=1;c<8;c++) {
// c (pre defined var) is left for
// thread 0 use only. So, no worry. og[t]=c;bli=1;mm(t,"xtc");
// (o) & xm("tc") are adjusted for }
// thread safety. Threads can't use }
// (bli) so only thread 0 uses it. if (blpg[t]==1) {
// All 7 threads share this block. for(int a=0;a<2;a++) {
// (a) is defined here so, no worry clsg[t]=" rgbcmpo".Substring(t,1)+"0";
// Form the color code for each thread osg[t]="Hello, this is thread number
"+t+".";mm(t,"t"); }
// (t) is unique for the thread, but }
// (cls),(os) are common. So, method mm() }
// uses t as is, but uses clsg[t],osg[t] }
// in place of cls,os. ========================================================================================= TUTORIAL:
The example has shown the basics necessary for writing a multi-thread program. The top 3 lines of method run() are common.
They must be included in all multi-thread program. They must be located above all block branching statements, so any thread,
regardless of its starting block number executes them.
As you have seen, each thread has introduced itself twice as wanted
and used the exact color which we wanted it to use. So there has been no data corruption. However, the execution sequence
is not the same as expected which is a feature we must live with when we work with multiple threads. However, if we have
included the full code into one of the PC# thread safe methods m0():m9() we could have got the the expected sequence. Here
is how it could have been done: ========================================================================================= public
class a:pcs {
public override void init() { tia=toa="t"; bli=0;
base.init(); } public override void run() { //---- The following three lines must
be included into any multi-thread program ----- int t;
// Thread # Defined here so each thread thp=Thread.CurrentThread;
// will have its own unique copy. t=Int32.Parse(thp.Name);
// Value of (t) for current thread //-----------------------------------------------------------------------------------
if (blpg[t]==0) { os="Hello, this is thread number zero, I'll be creating 7 threads.";tm();
os="Each thread will be introducing itself twice.";tm();
// No thread safety worry until now. for (c=1;c<8;c++) {
// c (pre defined var) is left for
// thread 0 use only. So, no worry. og[t]=c;bli=1;mm(t,"xtc");
// (o) & xm("tc") are adjusted for }
// thread safety. Threads can't use }
// (bli) so only thread 0 uses it. if (blpg[t]==1) {
// All 7 threads share this block. mm(t,"0");
// Access method m0() through method mm() } } public override
void m0(int t) { // Method m0() requires
thread number for(int a=0;a<2;a++) {
// You write this code as if it was cls=" rgbcmpo".Substring(t,1)+"0";
// made for single thread operation os="Hello, this is thread number "+t+".";tm();
} } } =========================================================================================

Example 1: 7 threads have been created and each one has displayed a
message introducing itself twice.
Although everything has worked properly, the sequence of events has not been the
same as expected.

Example 1a: Using the PC# method m0() has changed the sequence
of events to what we have expected while
maintaining thread safety.
Example 2: Let us now get into a more sophisticated application. Let
us see how we can do graphics in a multi-thread environment.
In example 4 of the drawing section, we have drawn the two sides of
an "Ace of Diamonds" Now we are going to try to use two threads to share the same job. One thread will draw the front
side and the other thread will draw the back side.
Based on what we have seen so far, all it takes to do this job should
be the use of group var's and the thread safe method mm(). Let us see how it works. =========================================================================================
public class a:pcs { public override void init() {
o=3;dm("ht");
// Allow the creation of upto 3 threads. bli=0; base.init(); }
public override void run() { //------ We have contracted the same repeated 3 lines into one to save
space ------- int t;thp=Thread.CurrentThread;t=Int32.Parse(thp.Name); //----------------------------------------------------------------------------------
if (blp==0) {
// Main thread (thread 0) start block og[t]=1;bli=1;mm(t,"xtc");
// Create thread #1 with starting block=1 og[t]=2;bli=2;mm(t,"xtc");
// Create thread #2 with starting block=2 } if (blp==1) {
// Thread 1 starting block //----------------------------- Card's Front side --------------------------------
jfg[t]=-150;kfg[t]=0;lfg[t]=224;ofg[t]=300;mm(t,"gcrd"); clsg[t]="r0";mm(t,"gsps");
fnsg[t]="trp24"; osg[t]="A";jfg[t]=-242;kfg[t]=130;mm(t,"gctf");
osg[t]="A";jfg[t]=-58;kfg[t]=-130;adg[t]=180;mm(t,"gctf"); jfg[t]=-150;kfg[t]=0;lfg[t]=ofg[t]=30;adg[t]=45;mm(t,"gcrf");
jfg[t]=-242;kfg[t]=110;lfg[t]=ofg[t]=15;adg[t]=45;mm(t,"gcrf"); jfg[t]=-58;kfg[t]=-110;lfg[t]=ofg[t]=15;adg[t]=45;mm(t,"gcrf");
} if (blp==2) {
// Thread 2 starting block //----------------------------- Card's Back side --------------------------------
clsg[t]="S9";mm(t,"gsps"); jfg[t]=+150;kfg[t]=0;lfg[t]=224;ofg[t]=300;mm(t,"gcrd");
clsg[t]="r0";ig[t]=5;mm(t,"gsps"); jfg[t]=150;kfg[t]=0;lfg[t]=209;ofg[t]=285;mm(t,"gcrd");
jfg[t]=150;kfg[t]=0;lfg[t]=199;ofg[t]=275;mm(t,"gcr"); clsg[t]="b0s9";adg[t]=-90;mm(t,"gspl");
mm(t,"ggrf"); JFG[t]=new float[]{150,50,250,150}; KFG[t]=new
float[]{138,-138,-138,138}; oig[t]=4;mm(t,"gcp"); clsg[t]="b0s9";adg[t]=90;mm(t,"gspl");
mm(t,"ggrf"); } } } =========================================================================================

Example 2: Performing graphic operations in a multi-thread environment
. Using seperate threads to
draw the two sides of the card - First try which has not been successful.
Look at what we got! Colors are messed up. So, what is the problem?
The problem is common objects. One thread created a red solid brush and before it has been used, the other thread created
a gradient blue-white paint brush. So colors have got mixed up.
Now, how can we solve this problem? There are plenty of objects involved
with graphics and they cost plenty of resources. Creating variable groups which allow each thread to use its own
unique variables for supplying data to all methods and getting all returned data from them has not been too costy since
numeric, boolean and string var's don't consume plenty of resources, but objects do.
One answer is using methods m0():m9() They fix any common resource
problem. So they should fix the common objects problem. So let us use m0() to draw the front side of the card and method
m1() to draw the back side. ========================================================================================= public
class a:pcs { public override void init() { o=3;dm("ht");
// Allow the creation of upto 3 threads. bli=0; base.init(); }
public override void run() { int t;thp=Thread.CurrentThread;t=Int32.Parse(thp.Name);
if (blp==0) {
// Main thread (thread 0) start block og[t]=1;bli=1;mm(t,"xtc");
// Create thread #1 with starting block=1 og[t]=2;bli=2;mm(t,"xtc");
// Create thread #2 with starting block=2 } if (blp==1) {
// Thread 1 starting block mm(t,"0");
// Call m0() through the thread safemethod mm() } if (blp==2) {
// Thread 2 starting block mm(t,"1");
// Call m1() through the thread safemethod mm() } }
//---- Note that the methodes use the same code which has been
used with single thread.
public override void m0(int t) { //-----------------------------
Card's Front side -------------------------------- jf=-150;kf=0;lf=224;of=300;gm("crd");
// Draw card outline rectangle cls="r0";gm("sps");
// Set color to pure red, solid pen/brush fns="trp24";
// Set font:TimesRoman, plain size=24 os="A";jf=-242;kf=130;gm("ctf");
// Draw the text "A" at top left os="A";jf=-58;kf=-130;ad=180;gm("ctf"); // Draw same rotated 180 degrees
// at bottom right corner. jf=-150;kf=0;lf=of=30;ad=45;gm("crf"); // Fill a 30X30 square at card's
center
// rotated 45 degrees. jf=-242;kf=110;lf=of=15;ad=45;gm("crf");// Same but smaller at top left corner
jf=-58;kf=-110;lf=of=15;ad=45;gm("crf");// and at bottom right corner }
public override void m1(int t) { //-----------------------------
Card's Back side -------------------------------- cls="S9";gm("sps");
// set color back to black solid jf=+150;kf=0;lf=224;of=300;gm("crd"); // Draw card outline
rectangle
cls="r0";i=5;gm("sps");
// Set pen color to red, size=5, solid jf=150;kf=0;lf=209;of=285;gm("crd"); // Draw
a rectangular frame.
jf=150;kf=0;lf=199;of=275;gm("cr");
// Create inner rectangle without drawing cls="b0s9";ad=-90;gm("spl");
// Set color to linear gradient changing
// from blue to white at an angle of 90 deg
// covering (gpp)'s bounding area. gm("grf");
// Render (gpp) and fill with color
JF=new float[]{150,50,250,150};
// Create Gen Path with points defined in KF=new float[]{138,-138,-138,138};
// JF[],KF[], Point Count=4. Don't draw it. oi=4;gm("cp"); cls="b0s9";ad=90;gm("spl");
// Create linear gradient color covering the
// new (gpp) object and changing colors at
// opposite direction gm("grf");
// Render (gpp) and fill with color } } =========================================================================================

Example 2a: First solution. Using methods m0() and m1() to correct the
thread safety problem which
has been caused by using common objects.
Wow. It worked great. These methods are very easy to write and seem
to do the job very well. At least this is one way to look at them.
But, this is not all. The reason they work so good is that when
the first thread started, it called method mm() As you know method mm() allows only one thread to access it at a time.
So the second thread had to wait until method mm() executed method m0() in full and finished the job for thread 1 before
it was allowed to get in. This means that the two card sides have not been drawn simultaneously which could have been why
we needed to use threads.
So, we now need to look for a way which can make the two threads work
on their drawings at the same time without causing data corruption.
The only way to accomplish this is to use the same variable grouping
method. However in the case of objects, we are going to let your program tell us exactly which objects are necessary
for each thread in order to waste no resources.
Method xm() at mode "tc" which creates all threads expects you to
supply it with a string assigned to (os) which is made of the names of all necessary objects for the thread you want
to create seperated with commas.
Now let us see which objects are necessary for each of the two threads.
Thread 1 creates rectangles using gm("cr") and paints them with solid
paint pens and brushes only. So it requires the present GraphicsPath object (gpp), the solid pen (spp) and the solid
brush (sbp)
Thread 2 does the same tasks, and additionally creates Linear Gradient
paint which requires object (lgp)
So, thread1 uses gpp,spp,sbp And thread2 uses gpp,spp,sbp,lgp
The first 3 objects are common so each thread should ask for its own
private copies of them in order to avoid data corruption. The fourth object (lgp) is used by one thread only. So we
cannot expect it to cause corruption. So there is no need to privatize it.
Now let see how to modify the code to allow for private gpp,spp,sbp. ========================================================================================= public
class a:pcs { public override void init() { o=3;dm("ht"); bli=0;
base.init(); } public override void run() { int t;thp=Thread.CurrentThread;t=Int32.Parse(thp.Name);
if (blp==0) { og[t]=1;osg[t]="gpp,spp,sbp";bli=1;mm(t,"xtc"); // Here
is the new change og[t]=2;osg[t]="gpp,spp,sbp";bli=2;mm(t,"xtc"); //
} // --------------- Everything from here down has not been changed ----------------
if (blp==1) {
// Thread 1 starting block //----------------------------- Card's Front side --------------------------------
jfg[t]=-150;kfg[t]=0;lfg[t]=224;ofg[t]=300;mm(t,"gcrd"); clsg[t]="r0";mm(t,"gsps");
fnsg[t]="trp24"; osg[t]="A";jfg[t]=-242;kfg[t]=130;mm(t,"gctf");
osg[t]="A";jfg[t]=-58;kfg[t]=-130;adg[t]=180;mm(t,"gctf"); jfg[t]=-150;kfg[t]=0;lfg[t]=ofg[t]=30;adg[t]=45;mm(t,"gcrf");
jfg[t]=-242;kfg[t]=110;lfg[t]=ofg[t]=15;adg[t]=45;mm(t,"gcrf"); jfg[t]=-58;kfg[t]=-110;lfg[t]=ofg[t]=15;adg[t]=45;mm(t,"gcrf");
} if (blp==2) {
// Thread 2 starting block //----------------------------- Card's Back side --------------------------------
clsg[t]="S9";mm(t,"gsps"); jfg[t]=+150;kfg[t]=0;lfg[t]=224;ofg[t]=300;mm(t,"gcrd");
clsg[t]="r0";ig[t]=5;mm(t,"gsps"); jfg[t]=150;kfg[t]=0;lfg[t]=209;ofg[t]=285;mm(t,"gcrd");
jfg[t]=150;kfg[t]=0;lfg[t]=199;ofg[t]=275;mm(t,"gcr"); clsg[t]="b0s9";adg[t]=-90;mm(t,"gspl");
mm(t,"ggrf"); JFG[t]=new float[]{150,50,250,150}; KFG[t]=new
float[]{138,-138,-138,138}; oig[t]=4;mm(t,"gcp"); clsg[t]="b0s9";adg[t]=90;mm(t,"gspl");
mm(t,"ggrf"); } } } =========================================================================================

Example 2b: Another solution for the same problem. Using private objects
for each thread.
Now we have accomplished both objectives. The two sides have been
drawn simultaneously and data corruption has been eliminated. =========================================================================================
INTRA-THREAD COMMUNICATION
In order to give threads the ability to share projects together, PC#
allows threads to send and receive messages to/from each other.
Each thread is assigned one mail box which can store only one message
string. A message string is made of the sender thread number, followed with a colon then followed with the message.
The mail box owner thread can receive its message by calling method
xm("mr"). This method obtains the sender thread number and the message which it returns to the owner thread assigned
to (o), (os) respectively. Then it erases the message string so that the mail box can receive a new message.
If the message was received successfully, (dnb=true) is also returned
to the owner thread together with (o) and (os). If no message was found in the box (dnb=false) is returned.
Messages are sent by calling method xm("ms"). If the message is sent
successfully, (dnb=true) will be returned to the sender thread. If the message could not be sent because the target
mail box was full, (dnb=false) is returned.
Here is the actual code for receiving an expected message:
dnbg[t]=false; // Initialize the
"job done flag" while (!dnbg[t]) { // Start a loop which terminates when message is received mm(t,"xtmr");
// Look for a message, if found receive it. Thread.Sleep(10); // Wait 10 milliseconds then repeat loop. }
And here is the actual code for sending a message:
// Assign the rcepient thread number to (o) and the message to (os)
before the loop dnbg[t]=false; // Initialize the "job done flag" while (!dnbg[t]) {
// Start a loop which terminates when message is sent mm(t,"xtms"); // Check recepient mail
box. If empty send message there. Thread.Sleep(10); // Wait 10 milliseconds then repeat loop. }
The two routines above for receiving and sending messages are general
and you'll need to use them over and over in your program each time you like a thread to receive or send a message.
We have contracted each of them in a single line to reduce the space they occupy. You can use "copy-paste" to insert each
of them into your code.
dnbg[t]=false;while (!dnbg[t]) {mm(t,"xtmr");Thread.Sleep(10);}//
Recv msg dnbg[t]=false;while (!dnbg[t]) {mm(t,"xtms");Thread.Sleep(10);}// Send msg ========================================================================================= Example
3: This Example shows how threads can work together to perform a task. The main thread is going to create 3 threads then
manage them to share the computation of the equation y = x^2 + 1. Thread 1 will generate a random number and assign it
to (x). Thread 2 will calculate (x^2) and thread 3 will add 1, coming up with the value of (y). ========================================================================================= public
class a:pcs { public override void init() { toa="t"; o=4;dm("ht");
// Allow the creation of upto 4 threads. bli=0; base.init(); }
public override void run() { int t;thp=Thread.CurrentThread;t=Int32.Parse(thp.Name);
if (blpg[t]==0) {
// Block 0 is for manager thread cls="r0"; os="Thread
0 creates 3 threads 1,2 and 3. The 3 threads share the calculation";tm(); os="of the equation
'y = x^2 + 1'. Thread 0 manages the operation through";tm(); os="internal messaging.";tm();
os="";tm();
for (c=1;c<4;c++) {
// Thrd 0 uses (c) & all pre-defined var's alone og[t]=c;bli=1;mm(t,"xtc");
// Create 3 threads, all start at blc 1. }
for (int c=1;c<4;c++) {
// Scan all 3 threads og[t]=c;osg[t]="Thread "+c+", Do your part.";// Send this
msg to each of them dnbg[t]=false;while (!dnbg[t]) {mm(t,"xtms");Thread.Sleep(10);}//
Send msg clsg[t]="b0";osg[t]="Sent to Thread "+c+": "+osg[t];mm(t,"t");
// Also display the same message
// Then attempt to receive reply dnbg[t]=false;while (!dnbg[t]) {mm(t,"xtmr");Thread.Sleep(10);}//
Recv msg clsg[t]="S9";osg[t]="Received from thread "+og[t]+": "+osg[t];mm(t,"t");
osg[t]="";mm(t,"t");
// Display reply, skip one line. } } else if
(blpg[t]==1) {
// Block 1 is for all other threads
dnbg[t]=false;while (!dnbg[t]) {mm(t,"xtmr");Thread.Sleep(10);}//
Try to Recv msg
// If msg received do the following: if (t==1) {
// If this is 1st thread ig[t]=100;mm(t,"umr");
// get a random number in (o), mm(t,"ofi");mm(t,"otd");
// Then convert it to double nd=odg[t];
// and assign it to (nd) osg[t]="Thread "+t+" reporting: The random number "+nd+"
has been assigned to x"; }
// Prepare msg to be sent to manager else if (t==2) {
// If this is 2nd thread jdg[t]=2;odg[t]=nd;mm(t,"ump");
// Calculate (nd^2) using (od) to nd=odg[t];
// perform the math osg[t]="Thread "+t+" reporting: Result of squaring operation
x^2="+nd; }
// Prepare msg to be sent to manager else if (t==3) {
// If this is 3rd thread nd++;
// Increment(nd) osg[t]="Thread "+t+" reporting: Equation complete. Result
(x^2+1)="+nd; }
// Prepare msg to be sent to manager og[t]=0;
// Msg recipient thread is 0 dnbg[t]=false;while (!dnbg[t]) {mm(t,"xtms");Thread.Sleep(10);}//
Send msg og[t]=t;mm(t,"xta");
// Then abort } } } =========================================================================================

Example 3: Shows how threads can share a project together.
HANDLING FILES IN A MULTI-THREAD ENVIRONMENT
Problems with files are not the same as problems with graphics. With
files you don't need to worry about objects. PC# creates the stream objects necessary for each file you open and keeps
them available until you close the file.
Any thread can open a file and any other thread can read or write
data to that file as long as it assigns the file keyname to (fs) before it calls the filing method fm().
So files are common for all threads and this creates a problem. You
can use private variables and use the thread safe method mm() when you read or write to a file, but this will not correct
all problems.
Let us suppose that you wrote a program in which each thread reads
data from a file, modifies it then writes it back into the same file. You must make sure that each thread completes
all three operations as a package before any other thread gets involved. If one thread reads the data then another thread
reads the same data before it was modified by the first one, the final result will not be the same.
let us have an example. We'll see what happens if we do the 3 operations
as a package using method m0() and what happens if we do them individually. ========================================================================================= Example
4: The main thread will create a "no header" Random Access File, open it and write "1" into record number 0. Then it will
create 6 threads. Each thread will read the same data, modifies it then writes it back. The even numbered threads will
modify the data by doubling its value and the odd numbered threads will modify the data by deviding its value by 2.
After all threads have finished, the main thread will read the ending data, display it then close the file.
If everything works fine we should expect the ending data to be 1.
Let us try doing the three operations as a package first. ========================================================================================= public
class a:pcs { public override void init() { toa="t"; bli=0;
base.init(); } public override void run() { int t;thp=Thread.CurrentThread;t=Int32.Parse(thp.Name);
if (blpg[t]==0) {
// Block 0 is for main thread fls="test.raf";fm("A");
// check file attributes if(os != " ") {ks="f";fm("D");}
// If exists, delete it fls="test.raf";fs="rf0";rcl=10;ib=true;fm("o");// open RAF, record
len=10,no header rcs=""+1;rci=0;fm("w");
// Write "1" into record # 0 os="Starting data:"+1;tm();
// Display starting data os="";tm();
// Skip one line for (c=1;c<7;c++) {
// Create 6 threads All start og[t]=c;bli=1;mm(t,"xtc");
// at block 1. } dnbg[t]=false;
// Initialize flag (Reset it) while (!dnbg[t]) {
// Start a loop which ends when all dnbg[t]=true;
// threads finish their jobs.Set flag for (int c=1;c<7;c++) {
// Scan all 6 threads og[t]=c;mm(t,"xts");
// Check state.If any neither stopped if ("sa".IndexOf(osg[t])<0)
dnbg[t]=false; // nor aborted reset flag back. Thread.Sleep(10);
// Pause to allow other threads share } }
fs="rf0";rci=0;fm("r");
// Read ending data. os="";tm();
// Skip one line os=rcs;om("c");
// Clean data os="Ending data="+os;tm();
// Display ending data fm("c");
// Close file } else if (blpg[t]==1) {
// Block 1 is for all created threads mm(t,"0");
// Execute m0() } } public override void m0(int t) { fs="rf0";rci=0;fm("r");
// Read data from file os=rcs;om("c");
// Clean data os="Thread number:"+t+" Data read:"+os;tm(); // Display read data
os=rcs;om("td");
// Convert string data to double if (t%2==0) od=od*2;
// If Thread # even, multiply by 2 else od=od/2;
// Else divide by 2 os="Thread number:"+t+" Data written:"+od;tm(); // Display data to be written
rcs=""+od;rci=0;fm("w");
// Write data into file } } =========================================================================================

Example 4: Shows how 6 threads can read, modify and write data
back into same file without corruption.
Everthing worked perfectly.
Now let us try to do them individually while insuring thread safety.
Replace block 1 with:
else if (blpg[t]==1) {
// Block 1 is for all created threads fsg[t]="rf0";rcig[t]=0;mm(t,"fr");
// Read data from file osg[t]=rcsg[t];mm(t,"oc");
// Clean data to be displayed osg[t]="Thread number:"+t+" Data read:"+osg[t];mm(t,"t");
// Display read data osg[t]=rcsg[t];
mm(t,"otd");
// Convert data to double if (t%2==0) odg[t]=odg[t]*2;
// If Thread # even, multiply by 2 else odg[t]=odg[t]/2;
// Else divide by 2 osg[t]="Thread number:"+t+" Data written:"+odg[t];mm(t,"t");
// Display data to be written rcsg[t]=""+odg[t];rcig[t]=0;mm(t,"fw");
// Write data into file } =========================================================================================

Example 4a: Shows how we can end with wrong results if we neglect to
do filing operations as a package
even if all thread safety measures have been taken.
As expected we have ended with wrong results caused by filing corruption. ========================================================================================= TUTORIAL:
Here are the rules which you need to know about threads:
(1) The top line in method run, before block branching must always
be: int t;thp=Thread.CurrentThread;t=Int32.Parse(thp.Name);
(2) If you need to declare new thread-safe variables, make your declaration
in method run. Each thread has its own private copy of all variables which are declared within method
run() while class top declared variables are common for all threads.
(3) The pre-declared variables which are assigned to your program
are not thread safe. Therefore we suggest using them in one of the following manners:
a) Limit their use to the main thread only (thread
0) b) Allow all threads to use them, but don't allow two threads to use the same one.
c) Allow all threads to share them, but make sure to lock the object while used.
(4) Whenever threads must use and modify a common resource like a
file, write your code into one of the 10 methods [m0():m9()]. Your code should not use thread-safe group
variables or method mm() Everything must be written as if it was made for single thread use. Threads
should not call these methods directly. They should be accessed through the thread-safe method mm().
(5) The block request variable (bli) is treated like one of the pre-declared
variables. It should be used by thread 0 only. All other threads are limited to one block so they
have no use for (bli). However (blp) should be used through var groups. All block branching statements
should be similar to this:
if (blpg[t]==n) {...} Where (n) is
the block number.
(6) When more than one thread execute a common loop, make sure to
include a "sleep" statement of (10 to 100) milli-seconds within the loop to insure that all threads
get a fair share.
(7) DO NOT use PC# methods to sleep the thread in a multi-thread environment.
To sleep the thread for n milli-seconds use the code:
Thread.Sleep(n);
If you like another thread to be able to wake it
up before the time has elapsed, use this code instead:
try {Thread.Sleep(n);}
catch(ThreadInterruptedException e) {exp=e;}
The thread which wakes it up should use the thread-safe
method mm() to interrupt the sleep process as follows:
og[t]=a;mm(t,"xti"); Where a=Number of thread to be awaken. =========================================================================================
|