|
Threading with Squirrel
An example of multi-threading application |
||||||
|
||||||
| Abstract | ||||||
|
In this article, we try to demonstrate how using Squirrel to build a multi-threading application can bring simplicity and power.
|
||||||
| 1. What is a thread ? | ||||||
|
Most of the modern operating system like Linux, BeOS or even Windows, offer the possibility of running several functions in paralle. Each of this function is called a thread and will run concurently to the other threads on the computer's CPU. In BeOS each application has the possibility to run as many thread as it need (in a certain limit). The set of threads of an application is called a team. All the threads of a team share the same memory and have access to the same datas. Although very powerfull, this could be dangerous when various threads access a unique ressource.
| ||||||
| 2. The application | ||||||
|
Consider using a BeIA as a device to control and monitor something like a temperature (BeIS is not yet available, but we'll assume it is for the following discussion). It could be the temperature of anything: an oven, a fridge or even a nuclear power plant. In our application, we going to simulate the monitoring of the exhaust temperature of an airplane engine. We will avoid getting into more detail than necessary for our discussion. Each engine will be controled by three detectors, all of which located in the exhaust pipe. These detectors will provide us the temperature in Farenheiht. Having three detectors instead of one is a security that offer some redundancy. The rules of the monitor are :
For all abnormal situations, an appropriate message must be displayed. The user will have control over both the real engines exhaust temperatures and the detectors faults.
|
||||||
| 3. The User Interface | ||||||
|
We going to use two windows for the GUI. One will display the engine's temperatures given by the detectors and the detector's status (correct or fault). The other window will give the user control over the simulation :
The Engine's exhaust monitor window is compose of two elements :
When the user select the item Start from the menu Sim, the item will be disabled and the item Stop will be enabled. When the Stop item is selected, the inverse will occur:
|
|
|
and |
|
They display both the temperature of the exhaust read by the detectors and the detector's status. The three green squares symbolize the status of the engine's detectors. Each of the squares turns red when a failure of the corresponding detector is .... detected ;)
Both of the boxes are build in the same way :
make "bleft Box 'Engine left' make "bright Box 'Engine right' /* the labels in the box */ make "temp.left LabeledText 'Real Temp :' "banner.t.left make "temp.right LabeledText 'Real Temp :' "banner.t.right make "det.left Frame $det.left~config "align.h "set "center make "det.right Frame $det.right~config "align.h "set "center make "e1d1.fail Frame "raised [10 10] $e1d1.fail~config "bgcolor "set :Green make "e1d2.fail Frame "raised [10 10] $e1d2.fail~config "bgcolor "set :Green make "e1d3.fail Frame "raised [10 10] $e1d3.fail~config "bgcolor "set :Green Glue :det.left "left [1 0] :e1d1.fail :e1d2.fail :e1d3.fail make "e2d1.fail Frame "raised [10 10] $e2d1.fail~config "bgcolor "set :Green make "e2d2.fail Frame "raised [10 10] $e2d2.fail~config "bgcolor "set :Green make "e2d3.fail Frame "raised [10 10] $e2d3.fail~config "bgcolor "set :Green Glue :det.right "left [1 0] :e2d1.fail :e2d2.fail :e2d3.fail Glue :bleft "top [] :temp.left :det.left Glue :bright "top [] :temp.right :det.right
to LabeledText :text :banner make.local "aframe Frame $aframe~config "expand.x "set true make.local "label Text :text make.local "value Banner :banner "right [7 0] $value~config "high.color "set :Blue Glue :aframe "left [] :label Glue :aframe "right [] :value output :aframe end
make "sim Window "titled 'Engine\'s exhaust monitor' [100 100] "not.zoomable "not.resizable
Hook :sim "quit {
$ctrl~quit
}
|
and |
|
For each engine, we want to be able to raise or lower the engine exhaust temperature and to simulate a failure on any of the three detectors. Each Box contains two Button widgets and three CheckBox widgets:
make "bleft Box 'Engine left'
make "bright Box 'Engine right'
make "b1 Button 'Raise'
Hook :b1 "invoked {
incr "engine1.temp 10
}
make "b2 Button 'Lower'
Hook :b2 "invoked {
incr "engine1.temp -10
if :engine1.temp < 0 {make "engine1.temp 0}
}
make "d1 CheckBox 'Detector 1' "e1d1
make "d2 CheckBox 'Detector 2' "e1d2
make "d3 CheckBox 'Detector 3' "e1d3
Glue :bleft "top [] :b1 :b2 :d1 :d2 :d3
make "b1 Button 'Raise'
Hook :b1 "invoked {
incr "engine2.temp 10
}
make "b2 Button 'Lower'
Hook :b2 "invoked {
incr "engine2.temp -10
if :engine2.temp < 0 {make "engine1.temp 0}
}
make "d1 CheckBox 'Detector 1' "e2d1
make "d2 CheckBox 'Detector 2' "e2d2
make "d3 CheckBox 'Detector 3' "e2d3
Glue :bright "top [] :b1 :b2 :d1 :d2 :d3
To simulate the engine control system, we going to use one thread for each detector and one thread for each engine. This thread will frequently check the detectors and display the engine's exhaust temperatures as well as any critical messages. A last thread will check the temperature of both engines and display a message when the difference between the two engines is greater than 100F.
Threads in Squirrel are accessible throught the Add-on Threading. The primitive Thread create a thread from a function or a block. The primitive Thread.hop start a thread (or severals threads). Other primitives allow to suspend, kill or wait for the end of a thread
The thread of the detectors will execute the function th.Detector. This function may or may not give the correct exhaust temperature:
to th.Detector :vtemp :vfail :engine
while (true) {
if (thing :vfail) {
make :vtemp (thing :engine) + (random 10 100)
} {
make :vtemp thing :engine
}
Snooze 700000
}
end
We need to loop forever in order to have this thread going until the simulation is stopped by the user, as the thread end when the function is over. The primitive Snooze will put the thread to sleep for 700000 microseconds.
The function th.Check will be used to check for the engine's exhaust temperature:
Here, the function loops forever and sleeps each iteration for 500000 microseconds. At each iteration, the difference between the temperatures read between the two engines is calculated. If this difference is significant, we will display an Info message box. The local variable err is used to avoid getting too many message boxes on screen.
to th.Check
make.local "err false
while (true) {
if (abs (:engine1.d - :engine2.d)) <= 100 {
if not :err {
make "err true
Info "stop ["ok] 'Engines not at the same temperature'
}
} {
make "err false
}
Snooze 500000
}
end
The thread cheking the detectors for an engine is the more complex:
to th.Engine :position :e.d :msg :l1 :l2 :l3 :real
local "lock "abnormal "fail1 "fail2 "fail3
/* first we extract the infos on the detector */
local "d1 "d2 "d3 "flag1 "flag2 "flag3 "t1 "t2 "t3 "f1 "f2 "f3
lscan :l1 "d1 "f1 "flag1
lscan :l2 "d2 "f2 "flag2
lscan :l3 "d3 "f3 "flag3
make :msg string 'Monitoring engine ' :position
make "fail1 false
make "fail2 false
make "fail3 false
while (true) {
/* we get the 3 reading of the detectors */
make "t1 thing :d1
make "t2 thing :d2
make "t3 thing :d3
if (:t1 = :t2) and (:t2 = :t3) {
/* no detector failing */
make :e.d :t1
make :real string :t1 ' F'
if :abnormal {
make "anbormal false
make :msg string 'Back to normal for ' :position
/* we need to set back the the failure flag of the detector */
if :fail1 {
make "fail1 false
UpdateFlag :flag1 :Green
}
if :fail2 {
make "fail2 false
UpdateFlag :flag2 :Green
}
if :fail3 {
make "fail3 false
UpdateFlag :flag3 :Green
}
}
} {
/* one or more of the detector had a failure */
if (:t1 <> :t2) and (:t2 <> :t3) and (:t1 <> :t3) {
/* all three of the detector are failing (or two of them) */
make :e.d av :t1 :t2 :t3
make :real string (thing :e.d) ' F'
make "abnormal true
make "fail1 true
make "fail2 true
make "fail3 true
UpdateFlag :flag1 :Red
UpdateFlag :flag2 :Red
UpdateFlag :flag3 :Red
make :msg string 'Failure of all the detectors on engine ' :position
if :Recycle {
make "th1 Thread "normal "th.Recycle :position 1 :f1
make "th2 Thread "normal "th.Recycle :position 2 :f2
make "th3 Thread "normal "th.Recycle :position 3 :f3
Thread.hop :th1 :th2 :th3
}
} {
if (:t1 <> :t2) and (:t1 <> :t3) and (:t2 = :t3) {
/* detector 1 failing */
make :e.d av :t2 :t3
make :real string (thing :e.d) ' F'
make :msg string 'Failure of detector 1 on engine ' :position
make "abnormal true
make "fail1 true
make "fail2 false
make "fail3 false
UpdateFlag :flag1 :Red
UpdateFlag :flag2 :Green
UpdateFlag :flag3 :Green
if :Recycle {
make "th1 Thread "normal "th.Recycle :position 1 :f1
Thread.hop :th1
}
} {
if (:t1 <> :t2) and (:t2 <> :t3) and (:t1 = :t3) {
/* detector 2 failing */
make :e.d av :t1 :t3
make :real string (thing :e.d) ' F'
make :msg string 'Failure of detector 2 on engine ' :position
make "abnormal true
make "fail2 true
make "fail1 false
make "fail3 false
UpdateFlag :flag2 :Red
UpdateFlag :flag1 :Green
UpdateFlag :flag3 :Green
if :Recycle {
make "th2 Thread "normal "th.Recycle :position 2 :f2
Thread.hop :th2
}
} {
/* detector 3 failing */
make :e.d av :t1 :t2
make :real string (thing :e.d) ' F'
make :msg string 'Failure of detector 3 on engine ' :position
make "abnormal true
make "fail3 true
make "fail1 false
make "fail2 false
UpdateFlag :flag3 :Red
UpdateFlag :flag1 :Green
UpdateFlag :flag2 :Green
if :Recycle {
make "th3 Thread "normal "th.Recycle :position 3 :f3
Thread.hop :th3
}
}
}
}
}
Snooze 400000
}
end
to UpdateFlag :flag :color $flag~config "bgcolor "set :color end
to th.Recycle :e :n :failure
/* first we wait for 0.5 second to simulate a recycle */
Snooze 500000
/* then we use a random value to see if the widget is failing still or not */
if (random 0 9) > 5 {
make :failure false
}
end
Creating the various thread is simply done by calling the primitive Thread:
make "lst.e1d1 list "engine1.d1 "e1d1.failure :e1d1.fail make "lst.e1d2 list "engine1.d2 "e1d2.failure :e1d2.fail make "lst.e1d3 list "engine1.d3 "e1d3.failure :e1d3.fail make "lst.e2d1 list "engine2.d1 "e2d1.failure :e2d1.fail make "lst.e2d2 list "engine2.d2 "e2d2.failure :e2d2.fail make "lst.e2d3 list "engine2.d3 "e2d3.failure :e2d3.fail make "th.e1d1 Thread "normal "th.Detector "engine1.d1 "e1d1.failure "engine1.temp make "th.e1d2 Thread "normal "th.Detector "engine1.d2 "e1d2.failure "engine1.temp make "th.e1d3 Thread "normal "th.Detector "engine1.d3 "e1d3.failure "engine1.temp make "th.e2d1 Thread "normal "th.Detector "engine2.d1 "e2d1.failure "engine2.temp make "th.e2d2 Thread "normal "th.Detector "engine2.d2 "e2d2.failure "engine2.temp make "th.e2d3 Thread "normal "th.Detector "engine2.d3 "e2d3.failure "engine2.temp make "th.engine1 Thread "normal "th.Engine "left "engine1.d "Message1 :lst.e1d1 :lst.e1d2 :lst.e1d3 "banner.t.left make "th.engine2 Thread "normal "th.Engine "right "engine2.d "Message2 :lst.e2d1 :lst.e2d2 :lst.e2d3 "banner.t.right make "th.check Thread "normal "th.Check
make "i.start $simu~add "Start {
$simu~i.enable :i.start false
$simu~i.enable :i.stop true
/* starting the simulation */
Thread.hop :th.e1d1 :th.e1d2 :th.e1d3 :th.e2d1 :th.e2d2 :th.e2d3
Thread.hop :th.engine1 :th.engine2 :th.check
}
make "i.stop $simu~add "Stop {
$simu~i.enable :i.start true
$simu~i.enable :i.stop false
Thread.suspend :th.e1d1 :th.e1d2 :th.e1d3 :th.e2d1 :th.e2d2 :th.e2d3
Thread.suspend :th.engine1 :th.engine2 :th.check
}
When we start the simulation, all the threads start to run:
If we had set the automatic recycle of the failing detector, we would have gotten:
In this case, the recycling was succesfull. However it may take longer for the detector to read back the real temperature.
When two detectors are failing, it's impossible to know the exact temperature of the exhaust. In this case, all the detector are marked at fault and their average value is displayed:
Note that the temperature is changed at each iteration of the thread th.Engine to reflect the change of the detector values.
If we now increase the temperature of the right engine we will get an info message box when the difference is greater than 100F:
$lk1~lock make "t1 thing :d1 $lk1~unlock $lk2~lock make "t2 thing :d2 $lk2~unlock $lk3~lock make "t3 thing :d3 $lk3~unlock
to th.Detector :lock :vtemp :vfail :engine
while (true) {
if (thing :vfail) {
$lock~lock
make :vtemp (thing :engine) + (random 10 100)
$lock~unlock
} {
$lock~lock
make :vtemp thing :engine
$lock~unlock
}
Snooze 700000
}
end
Althought easier to create than in C++, a multi-threading application in Squirrel still requies careful thought in design to perform with success.