Water 5-Concurrent Programming-ThreadContract| Return type | wob | | Parameter key | Default value | Type | | a_lock | req | thing | | Parameter kind | Default value | Type | | Other unkeyed arguments | opt with ekind of string | wob | | Water Contract<method thread.do_solo
a_lock=req=thing
_other_unkeyed=opt=wob=ekind.string="_body"/> | |
do_solo is very similar to Java's synchronize block.
It allows you to solve a subtle problem in multi-threaded programs.
Two (or more) threads can call the same body of code at the same time.
Sometimes this is desirable. You may have a generally useful utility
that more than one thread can use and there's no reason why they shouldn't
be able to. Other times there are "critical sections" where you want
some body of code to execute without interruption.
For example, say you are testing the value of a variable and, based on
the value, will do something.
<method compute_score>
<if> thing.count.<more 0/>
<do thing.<set score=100.<divide thing.count/> />
thing.<set count=0/>
/>
</if>
</method>
In your code you've got various places, which may run in various threads,
that initialize count with thing.<set count=0/>
as well as call thing.<increment "count"/>
Now we have 2 or more threads that call compute_score.
Let's say that it just so happens that 2 threads call compute_score
at about the same time.
The first thread calls compute_score, executes thing.count.<more 0/>
and gets swapped out by the OS time-slicer.
The second thread calls compute_score and runs through completion of
the method call before getting swapped out.
Then the first thread is resumed and tries to execute
thing.<set score=100.<divide count/> />
but errors with a divide by zero error because thing.<set count=0/>
had been run in the second thread.
The solution is to prevent any other thread from running our code
once we start it going so that no thread can sneak in between
thing.count.<more 0/> and 100.<divide count/>
We can do this by wrapping our "critical code" with a call to 'do_solo'
like so:
<method compute_score>
thread.<do_solo "lock123">
<if> thing.<has "count"/>.<and thing.count.<more 0/>/>
<do thing.<set score=100.<divide thing.count/> />
thing.<set count=0/>
/>
</if>
</do_solo>
</method>
<compute_score/>
Now when we call compute_score , if a previous thread is in the middle of running
the code, the current thread will wait until that thread is done before
running the body of the code. Since the first thread will set count to zero,
the second thread will call thing.count.<more 0/> and have it return false,
so the call to 'do' will not be run.
But what's this "lock123" about? In this case its not so important
since we never want this code to be run by more than one thread at a time.
But sometimes you have code that it is ok to have run by more than one
thread under certain conditions. The lock mechanism says that
only code holding the lock can run the code.
Only one thread can hold a lock at a given time.
do_solo will pause any thread not holding the lock.
If the lock is free, do_solo will first grab the lock, then run the code,
then release the lock.
Here's an example. Say we have a boat class with two methods,
launch and a method refuel.
<class boat name=req engine_on=false gas=10>
<method launch>
<echo "launching " .name/>
.<set engine_on=true/>
</method>
<method refuel>
<echo "refueling " .name/>
.<set engine_on=false/>
.<set gas=10/>
</method>
</class>
We want to be sure that the engine is off when we add gas.
But with multiple threads, it is possible that a launch method
could run in between our .<set engine_on=false/> and
.<set gas=10/>
Now it is ok for this to happen provided that it is a different boat being
launched than being refueled.
So here's how we can protect our code:
<class boat name=req engine_on=false gas=10>
<method launch>
thread.<do_solo _subject>
<echo "launching " .name/>
.<set engine_on=true/>
thread.<wait_until_time a_time=100/>
</do_solo>
</method>
<method refuel>
thread.<do_solo _subject>
<echo "refueling " .name/>
.<set engine_on=false/>
.<set gas=10/>
</do_solo>
</method>
</class>
Now when the launch method is called in the middle of our refuel method,
it checks to see if it can get the lock for the particular boat in question.
If so, it can run. If not, it pauses until the lock is available, which
will probably mean that the call to refuel has completed.
Our surrounding code might look something like:
<method boat.run_a_boat name=req>
<set a_boat=<boat name/>/>
5.<for_each>
a_boat.<launch/>
a_boat.<refuel/>
</for_each>
</method>
<thread> boat.<run_a_boat "boat1"/> </thread>.<start/>
<thread> boat.<run_a_boat "boat2"/> </thread>.<start/>
Operating systems have different ways of time slicing and that affects
the order of execution of the items in this example.
On Windows XP, we've found that the first iteration of the launch
of boat1 runs, then all iterations of boat2 run, then
the remaining iterations of boat1 run.
On Windows ME we've noticed that the last 4 iterations of
boat1 don't run at all. For interleaving processes, we
recommend using thread.wait_until_condition .
© Copyright 2007 Clear Methods, Inc.