/*
Copyright 2006 by Sean Luke and George Mason University
Licensed under the Academic Free License version 3.0
See the file "LICENSE" for more information
*/
package sim.engine;
/** Spawns all the sequence elements in parallel on separate threads.
This should ONLY be used if you know that all of the elements in
the sequence can be executed independently of one another without
any race conditions. No synchronization on the model data is done --
you're responsible for that if you need it.
<p>For example, keep in mind that the random number generator is unsynchronized.
You should not embed RandomSequences inside a ParallelSequence unless
you've set their shouldSynchronize value to true, and elsewhere in your
embedded steppables you're synchronizing on the random number generator.
<!-- This is false: we're doing wait(), which releases locks held.
<p>Also, keep in mind that the main thread which fired the ParallelSequence is still
holding onto the schedule lock; thus inside the ParallelSequence threads you can't
schedule anything (the Schedule is synchronized).
-->
<p>ParallelSequences are lightweight: they reuse the same threads
if stepped repeatedly. This means that you must never attach a ParallelSequence
inside itself -- that'd be an infinite loop, but it also would create weird thread
errors.
<p>Because ParallelSequences are lightweight, their threads are persistent -- even
after your main() loop exits! So if you use ParallelSequences, you have to remember to call
System.exit(0); to exit your program instead.
<p>While ParallelSequences might LOOK cool, generally speaking the only time you should
ever think to use them is if you actually HAVE multiple CPUs on your computer. Otherwise
they're almost certainly not the solution to your odd multiple-thread needs.
*/
public class ParallelSequence extends Sequence
{
Semaphore semaphore = new Semaphore(0);
Worker[] workers;
Thread[] threads;
boolean pleaseDie = false;
boolean operating = false; // checking for circularity
/// Threads are not serializable, so we must manually rebuild here
private void writeObject(java.io.ObjectOutputStream p)
throws java.io.IOException
{
p.writeObject(semaphore);
p.writeObject(workers);
// don't write the threads
p.writeBoolean(pleaseDie);
}
/// Threads are not serializable, so we must manually rebuild here
private void readObject(java.io.ObjectInputStream p)
throws java.io.IOException, ClassNotFoundException
{
semaphore = (Semaphore)(p.readObject());
workers = (Worker[])(p.readObject());
pleaseDie = p.readBoolean();
// recreate threads
buildThreads();
}
// creates the worker threads. used in the constructor and when reading the object from serialization.
// We presume that the existing threads are nonexistent -- we're loading from readObject or constructing.
void buildThreads()
{
threads = new Thread[steps.length];
for (int i = 0; i < steps.length; i++)
{
threads[i] = new Thread( workers[i] );
threads[i].start();
}
}
// sends all worker threads an EXIT signal
void gatherThreads()
{
pleaseDie = true;
for(int x=0;x<steps.length;x++)
workers[x].V();
for(int x=0;x<steps.length;x++)
try { threads[x].join(); }
catch (InterruptedException e) { /* shouldn't happen */ }
threads = null;
}
protected void finalize() throws Throwable
{
try { gatherThreads(); }
finally { super.finalize(); }
}
// sets up the worker threads. the number of steppable things the ParallelSequence handles cannot be modified after the constructor is called
public ParallelSequence(Steppable[] steps)
{
super(steps);
workers = new Worker[steps.length];
for( int i = 0 ; i < steps.length ; i++ )
workers[i] = new Worker();
buildThreads();
}
// steps once (in parallel) through the steppable things
public void step(final SimState state)
{
// just to be safe, we'll avoid the HIGHLY unlikely race condition of being stepped in parallel or nested here
synchronized(workers) // some random object we own
{
if (operating)
throw new RuntimeException("ParallelSequence stepped, but it's already in progress.\n" +
"Probably you have the same ParallelSequence nested, or the same ParallelSequence being stepped in parallel.\n" +
"Either way, it's a bug.");
operating = true;
}
for(int x=0;x<steps.length;x++)
{
workers[x].step = steps[x];
workers[x].state = state;
workers[x].V();
}
for(int x=0;x<steps.length;x++)
semaphore.P();
// don't need to synchronize to turn operating off
operating = false;
}
// a small semaphore class
static class Semaphore implements java.io.Serializable
{
private int count;
public Semaphore(int c) { count = c; }
public synchronized void V()
{
if (count == 0)
this.notify();
count++;
}
public synchronized void P()
{
while (count == 0)
try{ this.wait(); }
// we will do nothing if interrupted: just wait again. Note that
// the interrupted flag will be raised however when we leave. That
// should be fine.
catch(InterruptedException e) { }
count--;
}
// static inner classes don't need serialVersionUIDs
}
// a worker is a semaphore and also implements a runnable
class Worker extends Semaphore implements Runnable
{
Steppable step;
SimState state;
public Worker()
{
super(0);
}
public void run()
{
while(true)
{
P();
if (pleaseDie) return;
step.step(state);
semaphore.V();
}
}
// explicitly state a UID in order to be 'cross-platform' serializable
// because we ARE an inner class and compilers come up with all sorts
// of different UIDs for inner classes and their parents.
static final long serialVersionUID = -7832866872102525417L;
}
// explicitly state a UID in order to be 'cross-platform' serializable
// because we contain an inner class and compilers come up with all
// sorts of different UIDs for inner classes and their parents.
static final long serialVersionUID = 2731888904476273479L;
}