Personal C Sharp         by    famsoft.org

Home | Demonstrative Examples | PC# Methods | PC# Reference | Links
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();
    } 
  }
}
=========================================================================================

demo48.jpg

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.

demo49.jpg

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");
    }
  }
}
=========================================================================================

demo50.jpg

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
  }
}
=========================================================================================

demo51.jpg

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");
    }
  }
}
=========================================================================================

demo51.jpg

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
    }
  }
}
=========================================================================================

demo52.jpg

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
  }
}
=========================================================================================

demo53.jpg

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
    }
=========================================================================================

demo54.jpg

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.
=========================================================================================