/// <summary> Return active status of t. /// Per-thread active status can only be accessed and /// modified via synchronized method here in the group class. /// /// </summary> protected internal virtual bool GetActive(FJTaskRunner t) { lock (this) { return(t.active); } }
/* ------------ Methods called only by FJTaskRunners ------------- */ /// <summary> Return a task from entry queue, or null if empty. /// Called only by FJTaskRunner.scan(). /// /// </summary> protected internal virtual FJTask PollEntryQueue(FJTaskRunner runner) { try { object o = entryQueue.Poll(0); if (o is FJTask) { return(o as FJTask); } else if (o is InvokableCandidateFJTask) { InvokableCandidateFJTask i = o as InvokableCandidateFJTask; return(i.AsInvokableFJTask(runner)); } else if (o is IRunnable) { return(new FJTask.Wrap(o as IRunnable)); } else if (o == null) { return(null); } else { throw new ArgumentException("unexpected object in queue", o != null ? o.GetType().Name : "null"); } } catch (System.Threading.ThreadInterruptedException) { // ignore interrupts ThreadClass.Current.Interrupt(); return(null); } }
/// <summary> Set active status of thread t to false, and /// then wait until: (a) there is a task in the entry /// queue, or (b) other threads are active, or (c) the current /// thread is interrupted. Upon return, it /// is not certain that there will be work available. /// The thread must itself check. /// <p> /// The main underlying reason /// for these mechanics is that threads do not /// signal each other when they add elements to their queues. /// (This would add to task overhead, reduce locality. /// and increase contention.) /// So we must rely on a tamed form of polling. However, tasks /// inserted into the entry queue do result in signals, so /// tasks can wait on these if all of them are otherwise idle. /// </p> /// </summary> protected internal virtual void CheckActive(FJTaskRunner t, long scans) { lock (this) { Inactive = t; try { // if nothing available, do a hard wait if (activeCount_ == 0 && entryQueue.Peek() == null) { System.Threading.Monitor.Wait(this); } else { // If there is possibly some work, // sleep for a while before rechecking long msecs = scans / ScansPerSleep; if (msecs > MaxSleepTime) { msecs = MaxSleepTime; } int nsecs = (msecs == 0)?1:0; // forces shortest possible sleep System.Threading.Monitor.Wait(this, TimeSpan.FromMilliseconds(msecs + (nsecs / 1000))); } } catch (System.Threading.ThreadInterruptedException) { System.Threading.Monitor.Pulse(this); // avoid lost notifies on interrupts ThreadClass.Current.Interrupt(); } } }
/// <summary> Create all FJTaskRunner threads in this group. /// /// </summary> protected internal virtual void InitializeThreads() { for (int i = 0; i < threads.Length; ++i) { threads[i] = FJTaskRunner.New(this); } }
/// <summary> /// Create a new <see cref="FJTaskRunner"/>, setting its thread /// as needed /// </summary> public static FJTaskRunner New(FJTaskRunnerGroup runnerGroup) { FJTaskRunner runner = new FJTaskRunner(runnerGroup); Thread t = new Thread(new ThreadStart(runner.DoStart)); t.Name = "FJTaskRunner thread #" + t.GetHashCode(); runner.SetThread(t); return(runner); }
public FJTask AsInvokableFJTask(FJTaskRunner runner) { lock (group) { if (invokableFJTask == null) { invokableFJTask = new InvokableFJTask(r, group); } return(invokableFJTask); } }
/// <summary> Prints various snapshot statistics to System.out. /// <ul> /// <li> For each FJTaskRunner thread (labeled as T<em>n</em>, for /// <em>n</em> from zero to group size - 1):</li> /// <ul> /// <li> A star "*" is printed if the thread is currently active; /// that is, not sleeping while waiting for work. Because /// threads gradually enter sleep modes, an active thread /// may in fact be about to sleep (or wake up).</li> /// <li> <em>Q Cap</em> The current capacity of its task queue.</li> /// <li> <em>Run</em> The total number of tasks that have been run.</li> /// <li> <em>New</em> The number of these tasks that were /// taken from either the entry queue or from other /// thread queues; that is, the number of tasks run /// that were <em>not</em> forked by the thread itself.</li> /// <li> <em>Scan</em> The number of times other task</li> /// queues or the entry queue were polled for tasks. /// </ul> /// <li> <em>Execute</em> The total number of tasks entered /// (but not necessarily yet run) via execute or invoke.</li> /// <li> <em>Time</em> Time in seconds since construction of this /// FJTaskRunnerGroup.</li> /// <li> <em>Rate</em> The total number of tasks processed /// per second across all threads. This /// may be useful as a simple throughput indicator /// if all processed tasks take approximately the /// same time to run.</li> /// </ul> /// <p> /// Cautions: Some statistics are updated and gathered /// without synchronization, /// so may not be accurate. However, reported counts may be considered /// as lower bounds of actual values. /// Some values may be zero if classes are compiled /// with COLLECT_STATS set to false. (FJTaskRunner and FJTaskRunnerGroup /// classes can be independently compiled with different values of /// COLLECT_STATS.) Also, the counts are maintained as ints so could /// overflow in exceptionally long-lived applications. /// </p> /// <p> /// These statistics can be useful when tuning algorithms or diagnosing /// problems. For example: /// </p> /// <ul> /// <li> High numbers of scans may mean that there is insufficient /// parallelism to keep threads busy. However, high scan rates /// are expected if the number /// of Executes is also high or there is a lot of global /// synchronization in the application, and the system is not otherwise /// busy. Threads may scan /// for work hundreds of times upon startup, shutdown, and /// global synch points of task sets.</li> /// <li> Large imbalances in tasks run across different threads might /// just reflect contention with unrelated threads on a system /// (possibly including JVM threads such as GC), but may also /// indicate some systematic bias in how you generate tasks.</li> /// <li> Large task queue capacities may mean that too many tasks are being /// generated before they can be run. /// Capacities are reported rather than current numbers of tasks /// in queues because they are better indicators of the existence /// of these kinds of possibly-transient problems. /// Queue capacities are /// resized on demand from their initial value of 4096 elements, /// which is much more than sufficient for the kinds of /// applications that this framework is intended to best support.</li> /// </ul> /// </summary> public virtual void Stats(TextWriter writer) { long time = Utils.CurrentTimeMillis - initTime; //TODO: WARNING: Narrowing conversions may produce unexpected results in C#. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1042"' double secs = ((double)time) / 1000.0; long totalRuns = 0; long totalScans = 0; long totalSteals = 0; writer.Write("Thread" + "\tQ Cap" + "\tScans" + "\tNew" + "\tRuns" + "\n"); for (int i = 0; i < threads.Length; ++i) { FJTaskRunner t = threads[i]; int truns = t.runs; totalRuns += truns; int tscans = t.scans; totalScans += tscans; int tsteals = t.steals; totalSteals += tsteals; System.String star = (GetActive(t))?"*":" "; writer.Write("T" + i + star + "\t" + t.deqSize() + "\t" + tscans + "\t" + tsteals + "\t" + truns + "\n"); } writer.Write("Total" + "\t " + "\t" + totalScans + "\t" + totalSteals + "\t" + totalRuns + "\n"); writer.Write("Execute: " + entries); writer.Write("\tTime: " + secs); long rps = 0; if (secs != 0) { //TODO: WARNING: Narrowing conversions may produce unexpected results in C#. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1042"' rps = (long)System.Math.Round((double)(totalRuns) / secs); } writer.WriteLine("\tRate: " + rps); }
/// <summary> Set active status of thread t to true, and notify others /// that might be waiting for work. /// /// </summary> protected internal virtual void SetActive(FJTaskRunner t) { lock (this) { if (!t.active) { t.active = true; ++activeCount_; if (nstarted < threads.Length) { threads[nstarted++].Start(); } else { System.Threading.Monitor.PulseAll(this); } } } }
/// <summary> /// Create a new <see cref="FJTaskRunner"/>, setting its thread /// as needed /// </summary> public static FJTaskRunner New(FJTaskRunnerGroup runnerGroup) { FJTaskRunner runner = new FJTaskRunner(runnerGroup); Thread t = new Thread(new ThreadStart(runner.DoStart)); t.Name = "FJTaskRunner thread #" + t.GetHashCode(); runner.SetThread(t); return runner; }
/// <summary> Construct and return a FJTask object that, when executed, will /// invoke task1 and task2, in parallel /// /// </summary> public static FJTask NewPar(FJTask task1, FJTask task2, FJTaskRunner fjTaskRunner) { return(new Par2(task1, task2)); }
/// <summary> Construct and return a FJTask object that, when executed, will /// invoke the tasks in the tasks array in parallel using coInvoke /// /// </summary> public static FJTask NewPar(FJTask[] tasks, FJTaskRunner runner) { return(new Par(tasks)); }
/// <summary> Construct and return a FJTask object that, when executed, will /// invoke the tasks in the tasks array in array order /// </summary> public static FJTask NewSeq(FJTask[] tasks, FJTaskRunner fjTaskRunner) { return(new Seq(tasks)); }
/// <summary> Fork all tasks in array, and await their completion. /// Behaviorally equivalent to: /// <pre> /// for (int i = 0; i < tasks.length; ++i) tasks[i].fork(); /// for (int i = 0; i < tasks.length; ++i) tasks[i].join(); /// </pre> /// /// </summary> public void CoInvoke(FJTask[] tasks) { FJTaskRunner.coInvoke(tasks); }
/* ------------ Scheduling ------------------- */ /// <summary> Do all but the pop() part of yield or join, by /// traversing all DEQs in our group looking for a task to /// steal. If none, it checks the entry queue. /// <p> /// Since there are no good, portable alternatives, /// we rely here on a mixture of Thread.yield and priorities /// to reduce wasted spinning, even though these are /// not well defined. We are hoping here that the JVM /// does something sensible. /// </p> /// </summary> /// <param name="waitingFor">if non-null, the current task being joined /// /// </param> protected internal virtual void scan(FJTask waitingFor) { FJTask task = null; // to delay lowering priority until first failure to steal bool lowered = false; /* * Circularly traverse from a random start index. * * This differs slightly from cilk version that uses a random index * for each attempted steal. * Exhaustive scanning might impede analytic tractablity of * the scheduling policy, but makes it much easier to deal with * startup and shutdown. */ FJTaskRunner[] ts = group_.Array; int idx = victimRNG.Next(ts.Length); for (int i = 0; i < ts.Length; ++i) { FJTaskRunner t = ts[idx]; if (++idx >= ts.Length) { idx = 0; // circularly traverse } if (t != null && t != this) { if (waitingFor != null && waitingFor.Done) { break; } else { if (CollectStats) { ++scans; } task = t.take(); if (task != null) { if (CollectStats) { ++steals; } break; } else { if (Interrupted) { break; } else if (!lowered) { // if this is first fail, lower priority lowered = true; Priority = (ThreadPriority)scanPriority_; } else { // otherwise we are at low priority; just yield Thread.Sleep(0); } } } } } if (task == null) { if (CollectStats) { ++scans; } task = group_.PollEntryQueue(this); if (CollectStats) { if (task != null) { ++steals; } } } if (lowered) { Priority = (ThreadPriority)runPriority_; } if (task != null && !task.Done) { if (CollectStats) { ++runs; } task.Run(); task.SetDone(); } }
/// <summary> Set active status of thread t to true, and notify others /// that might be waiting for work. /// /// </summary> protected internal virtual void SetActive(FJTaskRunner t) { lock (this) { if (!t.active) { t.active = true; ++activeCount_; if (nstarted < threads.Length) threads[nstarted++].Start(); else System.Threading.Monitor.PulseAll(this); } } }
/// <summary> Return active status of t. /// Per-thread active status can only be accessed and /// modified via synchronized method here in the group class. /// /// </summary> protected internal virtual bool GetActive(FJTaskRunner t) { lock (this) { return t.active; } }
/// <summary> Construct and return a FJTask object that, when executed, will /// invoke task1 and task2, in order /// /// </summary> public static FJTask NewSeq(FJTask task1, FJTask task2, FJTaskRunner fjTaskRunner) { return new Seq2(task1, task2); }
/// <summary> Construct and return a FJTask object that, when executed, will /// invoke the tasks in the tasks array in parallel using coInvoke /// /// </summary> public static FJTask NewPar(FJTask[] tasks, FJTaskRunner runner) { return new Par(tasks); }
/// <summary> Same as scan, but called when current thread is idling. /// It repeatedly scans other threads for tasks, /// sleeping while none are available. /// <p> /// This differs from scan mainly in that /// since there is no reason to return to recheck any /// condition, we iterate until a task is found, backing /// off via sleeps if necessary. /// </p> /// /// </summary> protected internal virtual void scanWhileIdling() { FJTask task = null; bool lowered = false; long iters = 0; FJTaskRunner[] ts = group_.Array; int idx = victimRNG.Next(ts.Length); do { for (int i = 0; i < ts.Length; ++i) { FJTaskRunner t = ts[idx]; if (++idx >= ts.Length) { idx = 0; // circularly traverse } if (t != null && t != this) { if (CollectStats) { ++scans; } task = t.take(); if (task != null) { if (CollectStats) { ++steals; } if (lowered) { Priority = (ThreadPriority)runPriority_; } group_.SetActive(this); break; } } } if (task == null) { if (Interrupted) { return; } if (CollectStats) { ++scans; } task = group_.PollEntryQueue(this); if (task != null) { if (CollectStats) { ++steals; } if (lowered) { Priority = (ThreadPriority)runPriority_; } group_.SetActive(this); } else { ++iters; // Check here for yield vs sleep to avoid entering group synch lock if (iters >= FJTaskRunnerGroup.ScansPerSleep) { group_.CheckActive(this, iters); if (Interrupted) { return; } } else if (!lowered) { lowered = true; Priority = (ThreadPriority)scanPriority_; } else { Thread.Sleep(0); } } } }while (task == null); if (!task.Done) { if (CollectStats) { ++runs; } task.Run(); task.SetDone(); } }
/// <summary> Construct and return a FJTask object that, when executed, will /// invoke the tasks in the tasks array in array order /// </summary> public static FJTask NewSeq(FJTask[] tasks, FJTaskRunner fjTaskRunner) { return new Seq(tasks); }
/// <summary> Arrange for execution of a strictly dependent task. /// The task that will be executed in /// procedure-call-like LIFO order if executed by the /// same worker thread, but is FIFO with respect to other tasks /// forked by this thread when taken by other worker threads. /// That is, earlier-forked /// tasks are preferred to later-forked tasks by other idle workers. /// <p> /// Fork() is noticeably /// faster than start(). However, it may only /// be used for strictly dependent tasks -- generally, those that /// could logically be issued as straight method calls without /// changing the logic of the program. /// The method is optimized for use in parallel fork/join designs /// in which the thread that issues one or more forks /// cannot continue until at least some of the forked /// threads terminate and are joined. /// </p> /// </summary> public virtual void Fork() { FJTaskRunner.push(this); }
/// <summary> Set active status of thread t to false, and /// then wait until: (a) there is a task in the entry /// queue, or (b) other threads are active, or (c) the current /// thread is interrupted. Upon return, it /// is not certain that there will be work available. /// The thread must itself check. /// <p> /// The main underlying reason /// for these mechanics is that threads do not /// signal each other when they add elements to their queues. /// (This would add to task overhead, reduce locality. /// and increase contention.) /// So we must rely on a tamed form of polling. However, tasks /// inserted into the entry queue do result in signals, so /// tasks can wait on these if all of them are otherwise idle. /// </p> /// </summary> protected internal virtual void CheckActive(FJTaskRunner t, long scans) { lock (this) { Inactive = t; try { // if nothing available, do a hard wait if (activeCount_ == 0 && entryQueue.Peek() == null) { System.Threading.Monitor.Wait(this); } else { // If there is possibly some work, // sleep for a while before rechecking long msecs = scans / ScansPerSleep; if (msecs > MaxSleepTime) msecs = MaxSleepTime; int nsecs = (msecs == 0)?1:0; // forces shortest possible sleep System.Threading.Monitor.Wait(this, TimeSpan.FromMilliseconds(msecs + (nsecs / 1000))); } } catch (System.Threading.ThreadInterruptedException) { System.Threading.Monitor.Pulse(this); // avoid lost notifies on interrupts ThreadClass.Current.Interrupt(); } } }
/// <summary> Allow the current underlying FJTaskRunner thread to process other tasks. /// <p> /// Spinloops based on yield() are well behaved so long /// as the event or condition being waited for is produced via another /// FJTask. Additionally, you must never hold a lock /// while performing a yield or join. (This is because /// multiple FJTasks can be run by the same Thread during /// a yield. Since java locks are held per-thread, the lock would not /// maintain the conceptual exclusion you have in mind.) /// </p> /// <p> /// Otherwise, spinloops using /// yield are the main construction of choice when a task must wait /// for a condition that it is sure will eventually occur because it /// is being produced by some other FJTask. The most common /// such condition is built-in: join() repeatedly yields until a task /// has terminated after producing some needed results. You can also /// use yield to wait for callbacks from other FJTasks, to wait for /// status flags to be set, and so on. However, in all these cases, /// you should be confident that the condition being waited for will /// occur, essentially always because it is produced by /// a FJTask generated by the current task, or one of its subtasks. /// </p> /// </summary> public void Yield() { FJTaskRunner.taskYield(); }
/* ------------ Methods called only by FJTaskRunners ------------- */ /// <summary> Return a task from entry queue, or null if empty. /// Called only by FJTaskRunner.scan(). /// /// </summary> protected internal virtual FJTask PollEntryQueue(FJTaskRunner runner) { try { object o = entryQueue.Poll(0); if (o is FJTask) { return o as FJTask; } else if (o is InvokableCandidateFJTask) { InvokableCandidateFJTask i = o as InvokableCandidateFJTask; return i.AsInvokableFJTask(runner); } else if (o is IRunnable) { return new FJTask.Wrap(o as IRunnable); } else if (o == null) { return null; } else { throw new ArgumentException("unexpected object in queue", o != null ? o.GetType().Name : "null"); } } catch (System.Threading.ThreadInterruptedException) { // ignore interrupts ThreadClass.Current.Interrupt(); return null; } }
/// <summary> Yield until this task isDone. /// Equivalent to <code>while(!isDone()) yield(); </code> /// </summary> public virtual void Join() { FJTaskRunner.taskJoin(this); }
public FJTask AsInvokableFJTask(FJTaskRunner runner) { lock (group) { if (invokableFJTask == null) { invokableFJTask = new InvokableFJTask(r, group); } return invokableFJTask; } }
/// <summary> Fork both tasks and then wait for their completion. It behaves as: /// <pre> /// task1.fork(); task2.fork(); task2.join(); task1.join(); /// </pre> /// As a simple classic example, here is /// a class that computes the Fibonacci function: /// <pre> /// public class Fib extends FJTask { /// /// // Computes fibonacci(n) = fibonacci(n-1) + fibonacci(n-2); for n> 1 /// // fibonacci(0) = 0; /// // fibonacci(1) = 1. /// /// // Value to compute fibonacci function for. /// // It is replaced with the answer when computed. /// private volatile int number; /// /// public Fib(int n) { number = n; } /// /// public int getAnswer() { /// if (!isDone()) throw new Error("Not yet computed"); /// return number; /// } /// /// public void run() { /// int n = number; /// if (n > 1) { /// Fib f1 = new Fib(n - 1); /// Fib f2 = new Fib(n - 2); /// /// coInvoke(f1, f2); // run these in parallel /// /// // we know f1 and f2 are computed, so just directly access numbers /// number = f1.number + f2.number; /// } /// } /// /// public static void main(String[] args) { // sample driver /// try { /// int groupSize = 2; // 2 worker threads /// int num = 35; // compute fib(35) /// FJTaskRunnerGroup group = new FJTaskRunnerGroup(groupSize); /// Fib f = new Fib(num); /// group.invoke(f); /// int result = f.getAnswer(); /// System.out.println(" Answer: " + result); /// } /// catch (InterruptedException ex) { /// System.out.println("Interrupted"); /// } /// } /// } /// </pre> /// /// </summary> public void CoInvoke(FJTask task1, FJTask task2) { FJTaskRunner.coInvoke(task1, task2); }