Richard Suchenwirth 2005-04-30 - The M.U. & Tex. Railroad is a simple visualisation of
mutex, or mutually exclusive
semaphores (nl: seinpalen [
1]) that control the works of concurrent processes, which are here displayed as "trains" (longish rectangles, rather).
The railway has two semaphores, A and B. A train may only pass a semaphore if given the green light. To make sure there's always at most one train on the line between A and B, a semaphore is turned to red when a train passes it - it "obtains a mutex lock" on the semaphore. This operation is called P (nl: passeren, "pass"; or "prolaag" which I can't explain) in mutex theory.
When the train leaves the protected block, at semaphore B, it "releases the lock" on A, so that is turned green again - the V operation (nl: vrijgeven, "release"; or "verhoog"). And of course it obtains a lock on B to prevent collisions :)
proc main {} {
set w [canvas .c -width 700 -height 100]
pack $w -fill both -expand 1
$w create line 0 80 700 80
semaphore A $w 100 80
turn A red
$w create text 120 90 -text "P(A)"
semaphore B $w 600 80
turn B green
$w create text 630 90 -text "P(B); V(A)"
train $w 200 80
train $w -500 80
every 100 [list animate $w]
}
proc semaphore {name w x y} {
global g
$w create line $x $y $x [- $y 30] -width 2
$w create rect [- $x 5] [- $y 30] [+ $x 5] [- $y 50] -fill black
set g($name,top) [lamp $w $x [- $y 45]]
set g($name,bot) [lamp $w $x [- $y 35]]
set g($name,x) $x
lappend g(semaphores) $name
$w create rect [- $x 5] [- $y 10] [+ $x 5] [- $y 25] -fill white
$w create text $x [- $y 18] -text $name
set g(w) $w
}
proc lamp {w x y} {
$w create oval [- $x 4] [- $y 4] [+ $x 4] [+ $y 4]
}
proc train {w x y} {
set color [lpick {brown gray50 orange bisque}]
$w create rect $x $y [+ $x 250] [- $y 30] -fill $color -tag train
$w lower train
}
#-- This routine is called in fixed time intervals
proc animate w {
foreach train [$w find withtag train] {
set xmax [lindex [$w bbox $train] 2]
if {$xmax > 1200} {
$w delete $train
train $w -200 80
V B
}
if [semaphoreAhead $xmax name] {
if {$::g($name,state) eq "red"} continue
after 500 [list P $name]
if {$name eq "B"} {after 2500 {V A}}
}
$w move $train [expr {rand()*5+10}] 0
}
}
#-- Returns 1 if a semaphore is ahead, and gives its name in a variable
proc semaphoreAhead {xmax _var} {
upvar 1 $_var var
foreach sema $::g(semaphores) {
set dx [- $::g($sema,x) $xmax]
if {$dx > 0 && $dx < 30} {set var $sema; return 1}
}
return 0
}
#-- Dijkstra's classic mutex operations are very simple here:
proc P name {turn $name red}
proc V name {turn $name green}
proc turn {name color} {
if {$color eq "red"} {
set c1 red; set c2 black
} else {
set c1 black; set c2 green
}
$::g(w) itemconfig $::g($name,top) -fill $c1
$::g(w) itemconfig $::g($name,bot) -fill $c2
set ::g($name,state) $color
}
#-- Generally useful routines: prefix math, etc.
foreach op {+ - * /} {proc $op {a b} "expr {\$a $op \$b}"}
proc every {ms body} {eval $body; after $ms [info level 0]}
proc lpick list {lindex $list [expr {int(rand()*[llength $list])}]}
#-- Let's go!
main
#-- Very useful development helper:
bind . <Escape> {exec wish $argv0 &; exit}