Files
linux-packaging-mono/mcs/class/Mono.Parallel/Mono.Threading.Tasks/ThreadWorker.cs

422 lines
12 KiB
C#
Raw Normal View History

// ThreadWorker.cs
//
// Copyright (c) 2008 Jérémie "Garuma" Laval
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//
#if NET_4_0
using System;
using System.Threading;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Watch = System.Diagnostics.Stopwatch;
namespace Mono.Threading.Tasks
{
public class ThreadWorker : IDisposable
{
Thread workerThread;
/* This field is used when a TheadWorker have to call Task.Wait
* which bring him back here with the static WorkerMethod although
* it's more optimized for him to continue calling its own WorkerMethod
*/
[ThreadStatic]
static ThreadWorker autoReference;
readonly IConcurrentDeque<Task> dDeque;
readonly ThreadWorker[] others;
readonly ManualResetEvent waitHandle;
readonly IProducerConsumerCollection<Task> sharedWorkQueue;
readonly ThreadPriority threadPriority;
// Flag to tell if workerThread is running
int started = 0;
readonly int workerLength;
readonly int workerPosition;
const int maxRetry = 3;
const int sleepThreshold = 100;
int deepSleepTime = 8;
readonly Action<Task> adder;
Task currentTask;
public ThreadWorker (ThreadWorker[] others,
int workerPosition,
IProducerConsumerCollection<Task> sharedWorkQueue,
IConcurrentDeque<Task> dDeque,
ThreadPriority priority,
ManualResetEvent handle)
{
this.others = others;
this.dDeque = dDeque;
this.sharedWorkQueue = sharedWorkQueue;
this.workerLength = others.Length;
this.workerPosition = workerPosition;
this.waitHandle = handle;
this.threadPriority = priority;
this.adder = new Action<Task> (ChildWorkAdder);
InitializeUnderlyingThread ();
}
protected virtual void InitializeUnderlyingThread ()
{
this.workerThread = new Thread (WorkerMethodWrapper);
this.workerThread.IsBackground = true;
this.workerThread.Priority = threadPriority;
this.workerThread.Name = "ParallelFxThreadWorker";
}
public virtual void Dispose ()
{
Stop ();
if (workerThread.ThreadState != ThreadState.Stopped)
workerThread.Abort ();
}
public virtual void Pulse ()
{
if (started == 1)
return;
// If the thread was stopped then set it in use and restart it
int result = Interlocked.Exchange (ref started, 1);
if (result != 0)
return;
if (this.workerThread.ThreadState != ThreadState.Unstarted) {
InitializeUnderlyingThread ();
}
workerThread.Start ();
}
public virtual void Stop ()
{
// Set the flag to stop so that the while in the thread will stop
// doing its infinite loop.
started = 0;
}
// This is the actual method called in the Thread
protected virtual void WorkerMethodWrapper ()
{
int sleepTime = 0;
autoReference = this;
bool wasWokenUp = false;
// Main loop
while (started == 1) {
bool result = false;
result = WorkerMethod ();
if (!result && wasWokenUp)
waitHandle.Reset ();
wasWokenUp = false;
Thread.Yield ();
if (result) {
deepSleepTime = 8;
sleepTime = 0;
continue;
}
// If we are spinning too much, have a deeper sleep
if (++sleepTime > sleepThreshold && sharedWorkQueue.Count == 0) {
wasWokenUp = waitHandle.WaitOne ((deepSleepTime = deepSleepTime >= 0x4000 ? 0x4000 : deepSleepTime << 1));
}
}
started = 0;
}
// Main method, used to do all the logic of retrieving, processing and stealing work.
protected virtual bool WorkerMethod ()
{
bool result = false;
bool hasStolenFromOther;
do {
hasStolenFromOther = false;
Task value;
// We fill up our work deque concurrently with other ThreadWorker
while (sharedWorkQueue.Count > 0) {
waitHandle.Set ();
while (sharedWorkQueue.TryTake (out value)) {
dDeque.PushBottom (value);
}
// Now we process our work
while (dDeque.PopBottom (out value) == PopResult.Succeed) {
waitHandle.Set ();
ExecuteTask (value, ref result);
}
}
// When we have finished, steal from other worker
ThreadWorker other;
// Repeat the operation a little so that we can let other things process.
for (int j = 0; j < maxRetry; ++j) {
int len = workerLength + workerPosition;
// Start stealing with the ThreadWorker at our right to minimize contention
for (int it = workerPosition + 1; it < len; ++it) {
int i = it % workerLength;
if ((other = others [i]) == null || other == this)
continue;
// Maybe make this steal more than one item at a time, see TODO.
while (other.dDeque.PopTop (out value) == PopResult.Succeed) {
if (!hasStolenFromOther)
waitHandle.Set ();
hasStolenFromOther = true;
ExecuteTask (value, ref result);
}
}
}
} while (sharedWorkQueue.Count > 0 || hasStolenFromOther);
return result;
}
void ExecuteTask (Task value, ref bool result)
{
if (value == null)
return;
var saveCurrent = currentTask;
currentTask = value;
value.Execute (adder);
result = true;
currentTask = saveCurrent;
}
// Almost same as above but with an added predicate and treating one item at a time.
// It's used by Scheduler Participate(...) method for special waiting case like
// Task.WaitAll(someTasks) or Task.WaitAny(someTasks)
// Predicate should be really fast and not blocking as it is called a good deal of time
// Also, the method skip tasks that are LongRunning to avoid blocking (Task are not LongRunning by default)
public static void ParticipativeWorkerMethod (Task self,
ManualResetEventSlim predicateEvt,
int millisecondsTimeout,
IProducerConsumerCollection<Task> sharedWorkQueue,
ThreadWorker[] others,
ManualResetEvent evt,
Func<Task, Task, bool> checkTaskFitness)
{
const int stage1 = 5, stage2 = 0;
int tries = 50;
WaitHandle[] handles = null;
Watch watch = Watch.StartNew ();
if (millisecondsTimeout == -1)
millisecondsTimeout = int.MaxValue;
bool aggressive = false;
bool hasAutoReference = autoReference != null;
Action<Task> adder = null;
while (!predicateEvt.IsSet && watch.ElapsedMilliseconds < millisecondsTimeout && !self.IsCompleted) {
// We try to execute the self task as it may be the simplest way to unlock
// the situation
if (self.Status == TaskStatus.WaitingToRun) {
self.Execute (hasAutoReference ? autoReference.adder : (Action<Task>)null);
if (predicateEvt.IsSet || watch.ElapsedMilliseconds > millisecondsTimeout)
return;
}
Task value;
// If we are in fact a normal ThreadWorker, use our own deque
if (hasAutoReference) {
var enumerable = autoReference.dDeque.GetEnumerable ();
if (adder == null)
adder = hasAutoReference ? autoReference.adder : (Action<Task>)null;
if (enumerable != null) {
foreach (var t in enumerable) {
if (t == null)
continue;
if (checkTaskFitness (self, t))
t.Execute (adder);
if (predicateEvt.IsSet || watch.ElapsedMilliseconds > millisecondsTimeout)
return;
}
}
}
int count = sharedWorkQueue.Count;
// Dequeue only one item as we have restriction
while (--count >= 0 && sharedWorkQueue.TryTake (out value) && value != null) {
evt.Set ();
if (checkTaskFitness (self, value) || aggressive)
value.Execute (null);
else {
if (autoReference == null)
sharedWorkQueue.TryAdd (value);
else
autoReference.dDeque.PushBottom (value);
evt.Set ();
}
if (predicateEvt.IsSet || watch.ElapsedMilliseconds > millisecondsTimeout)
return;
}
// First check to see if we comply to predicate
if (predicateEvt.IsSet || watch.ElapsedMilliseconds > millisecondsTimeout)
return;
// Try to complete other work by stealing since our desired tasks may be in other worker
ThreadWorker other;
for (int i = 0; i < others.Length; i++) {
if ((other = others [i]) == autoReference || other == null)
continue;
if (other.dDeque.PopTop (out value) == PopResult.Succeed && value != null) {
evt.Set ();
if (checkTaskFitness (self, value) || aggressive)
value.Execute (null);
else {
if (autoReference == null)
sharedWorkQueue.TryAdd (value);
else
autoReference.dDeque.PushBottom (value);
evt.Set ();
}
}
if (predicateEvt.IsSet || watch.ElapsedMilliseconds > millisecondsTimeout)
return;
}
/* Waiting is split in 4 phases
* - until stage 1 we simply yield the thread to let others add data
* - between stage 1 and stage2 we use ManualResetEventSlim light waiting mechanism
* - after stage2 we fall back to the heavier WaitHandle waiting mechanism
* - if really the situation isn't evolving after a couple of sleep, we disable
* task fitness check altogether
*/
if (--tries > stage1)
Thread.Yield ();
else if (tries >= stage2)
predicateEvt.Wait (ComputeTimeout (5, millisecondsTimeout, watch));
else {
if (tries == stage2 - 1)
handles = new [] { predicateEvt.WaitHandle, evt };
System.Threading.WaitHandle.WaitAny (handles, ComputeTimeout (1000, millisecondsTimeout, watch));
if (tries == stage2 - 10)
aggressive = true;
}
}
}
public static ThreadWorker AutoReference {
get {
return autoReference;
}
set {
autoReference = value;
}
}
protected IConcurrentDeque<Task> Deque {
get {
return dDeque;
}
}
protected ThreadWorker[] Others {
get {
return others;
}
}
protected ManualResetEvent WaitHandle {
get {
return waitHandle;
}
}
protected ThreadPriority Priority {
get {
return threadPriority;
}
}
protected int WorkerPosition {
get {
return workerPosition;
}
}
protected virtual void ChildWorkAdder (Task t)
{
dDeque.PushBottom (t);
waitHandle.Set ();
}
static int ComputeTimeout (int proposed, int timeout, Watch watch)
{
return timeout == int.MaxValue ? proposed : System.Math.Min (proposed, System.Math.Max (0, (int)(timeout - watch.ElapsedMilliseconds)));
}
public bool Finished {
get {
return started == 0;
}
}
public int Id {
get {
return workerThread.ManagedThreadId;
}
}
public virtual bool Equals (ThreadWorker other)
{
return (other == null) ? false : object.ReferenceEquals (this.dDeque, other.dDeque);
}
public override bool Equals (object obj)
{
ThreadWorker temp = obj as ThreadWorker;
return temp == null ? false : Equals (temp);
}
public override int GetHashCode ()
{
return workerThread.ManagedThreadId.GetHashCode ();
}
}
}
#endif