Jon Skeet said:
Willy Denoyette said:
And what if the CurrentThread never equals the myArrayList[0] thread?
*One* of the ones which is unblocked by a call to PulseAll (sorry about
using the Java terminology before!) will be. Then everything moves down
the list (a queue would be better than an ArrayList here) and next time
the next thread gets a go.
This what I meant in my previous reply, don't count on it, the CLR
doesn't
guarantee "fairness", so it's possible the thread your waiting for never
get
serviced.
AFAIK there is no solution to this in managed world.
There is, and it can be encapsulated, I'm sure. It's probably not a bad
thing to write a utility class for - I'll give it a go when I get some
time.
Jon, vooose,
You can't predict the unpredictable.
My point is that the CLR doesn't guarantee "fair thread" synchronization
when waiting for a common synchronization object (see my other postings).
Trying to synchronize threads in a fixed/predetermined order is highly
inefficient (or impossible).
To illustrate my point I made following code sample[1].
The main thread creates and starts x number of worker threads, and fills a
queue with work items. Each work item in this sample identifies the thread
that should handle the item.
The items are stored in ascending order starting with 0 up to noThreads.
Compile the code and run it, which the time it takes to handle the few items
and watch also the number of context switches!
To make it worse, just throw-in some IO calls (kernel transitions) or some
GC work (see code).
To look at the result when the work item is free to run on any thread,
un-comment the following block (and watch the thread sequence).
if (Convert.ToInt32(Thread.CurrentThread.Name) !=
(int)workToBeDone.Peek())
{
Monitor.Pulse(workToBeDone);
return null;
}
Willy.
[1]
// --- begin of code
using System;
using System.Text;
using System.Threading;
using System.Collections;
public class MyWorkQueue
{
// Work item queue
private Queue workToBeDone = new Queue();
public void AddWorkItem(object item)
{
// lock the worker queue
lock (workToBeDone)
{
workToBeDone.Enqueue(item);
// Notify a single worker (or all workers ).
Monitor.Pulse(workToBeDone);
// Monitor.PulseAll(workToBeDone);
}
}
public object GetWorkItemFromQueue()
{
lock (workToBeDone)
{
while (workToBeDone.Count == 0 )
{
// Block/Wait until pulsed
Monitor.Wait(workToBeDone);
// Handle possible race here.
// Another thread can have handled the item in between Pulse and Wait
returning.
if (workToBeDone.Count == 0)
Console.WriteLine("[{0}] other thread came in and did his job (woken
for nothing!)", Thread.CurrentThread.Name);
}
// OK, whe have an item, should we handle it?
if (Convert.ToInt32(Thread.CurrentThread.Name) !=
(int)workToBeDone.Peek())
{
// No, it's another thread's work, so pulse and return null, but keep the
item on the queue.
Monitor.Pulse(workToBeDone);
return null;
}
// it's ours, so remove item from the queue and return it.
return workToBeDone.Dequeue();
}
}
}
class Test
{
static MyWorkQueue q = new MyWorkQueue();
static int itemsToGo = 0;
static object locker = new object();
static StringBuilder sb = new StringBuilder();
static void Worker()
{
string n = Thread.CurrentThread.Name;
while (true)
{
object o;
if ((o = q.GetWorkItemFromQueue()) != null)
{
int i = (int) o;
lock (locker) { --itemsToGo; sb.Append(n);}
// Do your critical work here.
// Trow in some GC work in order to make it some more inefficient.
// GC.Collect(0);
}
}
}
static void Main()
{
// create workers
int noThreads = 4;
Thread[] ta = new Thread[noThreads];
for (int t = 0; t < noThreads ; t++)
{
ta[t] = new Thread (new ThreadStart(Worker));
ta[t].Name = t.ToString();
ta[t].IsBackground = true;
}
for (int t = 0; t < noThreads ; t++)
{
Console.WriteLine("Thread:{0} started", ta[t].Name);
ta[t].Start();
}
const int totalItems = 40;
while (true)
{
Thread.Sleep(500);
lock (locker)
{
// wait if still more than x work items to go
if (itemsToGo > 20) continue;
Console.WriteLine("{0}", itemsToGo);
itemsToGo += totalItems;
// dump thread sequence to console
Console.WriteLine(sb.ToString());
sb.Length = 0;
}
Console.WriteLine("Add more work");
int n = 0;
for (int i = 0; i < totalItems; ++i)
{
q.AddWorkItem(n++);
if (n == noThreads)
n = 0;
}
}
}
}// --- end of code