How to stop one thread until n threads have completed their work
I have an application with one main thread and N worker threads. At some point I need that the main thread waits until all the N threads have completed one section of their work.
I normally would use Monitor.Wait() and Monitor.Pulse() but this will prevent the N threads from working at the same time.
Any idea on how开发者_Go百科 to do that?
Thanks in advance.
.NET 4.0 will include the System.Threading.Barrier
class that will make synchronization between multiple threads easier. A blog post with some good example code can be found here.
Similar functionality can be achieved using multiple WaitHandles in .NET 3.0+, as demonstrated in this example on MSDN.
A brief summary of the MSDN example:
const int numberOfWorkers = 5;
static void Main()
{
var handles = new ManualResetEvent[numberOfWorkers];
for (int i = 0; i < numberOfWorkers; i++)
{
handles[i] = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(o => worker.Work(), null);
}
// Wait for all workers to finish before continuing
WaitHandle.WaitAll(handles);
/* continue execution... */
}
Do something similar to garbage collection. You'll write a ThreadManager that has a count of how many threads are running. When the main thread starts a new worker, the ThreadManager will increment its count of workers. When a worker finishes, it will inform the ThreadManager who will decrement its count of threads. When it has zero worker threads, the ThreadManager will wake the main thread.
It seems like WaitHandle.WaitAll
should solve this problem.
Your main thread will need to keep references to worker thread wait handles. When it needs to synchronize, pass those handles into the above method. Worker threads signal at the appropriate point in their code.
If worker threads loop or need to 'pulse' multiple times, you could use AutoResetEvents
, like this:
public void WorkerMethod() {
DoFirstThing();
this.autoResetEvent.Set();
DoSecondThing();
this.autoResetEvent.Set();
// etc.
}
If not (if the main thread just needed to know the worker thread had passed some threshold), ManualResetEvents
would be fine.
There are a few things to be wary of when using WaitAll (from the MSDN WaitAll
documentation):
On some implementations, if more than 64 handles are passed, a NotSupportedException is thrown. If the array contains duplicates, the call fails with a DuplicateWaitObjectException.
However, it's rare that a process can really take advantage of more than 64 threads, so this limitation often won't be important.
It's called a barrier: http://programmingexamples.wikidot.com/java-barrier
Ow, but if you only need the first thread to wait for the rest to pass some point and you want the other to still keep working, then use a semaphore of size N and let all other threads take it, while that first thread waits to acquire it after them..
Semaphore: http://programmingexamples.wikidot.com/java-semaphore
Since on some implementations, there is a limit to how many handles WaitHandle.WaitAll()
can .... handle, (see msdn-WaitHandle.WaitAll(), I have created a utility method for this:
public static void WaitAll(WaitHandle[] handles)
{
if (handles == null)
throw new ArgumentNullException("handles",
"WaitHandle[] handles was null");
foreach (WaitHandle wh in handles) wh.WaitOne();
}
usage is to add the wait handle for each thread to an array, and then call the above utility method (passing the array) after All threads have been initiated.
List<WaitHandle> waitHndls = new List<WaitHandle>();
foreach (MyType mTyp in MyTypeCollection)
{
ManualResetEvent txEvnt = new ManualResetEvent(false);
int qryNo1 = ++qryNo;
ThreadPool.QueueUserWorkItem(
delegate
{
try
{
// Code to execute whatever thread's function is...
}
catch (SomeCustomException iX)
{
// catch code
} }
finally { lock (locker) txEvnt.Set(); }
});
waitHndls.Add(txEvnt);
}
util.WaitAll(waitHndls.ToArray());
If you just need to wait until the threads terminate, how about Thread.Join
? In .NET 4.0 you could use Task.WaitAll
. If you need to wait until they finish just part of their task it's a little tricker. In current versions of .NET look at WaitHandle.WaitAll
/Threading.ManualResetEvent
. In .NET 4.0 you can use Threading.Barrier
.
Ok, what I'm doing now (using your ideas) and seems to work is this:
I declared a list of ManualResetEvent:
Private m_waitHandles As List(Of Threading.ManualResetEvent)
The process accepts incoming Tcp connections and starts one thread on each connection. So at the new client handler I've added this code:
Dim waitHandle As Threading.ManualResetEvent
waitHandle = New Threading.ManualResetEvent(True)
SyncLock (DirectCast(m_waitHandles, IList).SyncRoot)
m_waitHandles.Add(waitHandle)
End SyncLock
''# Do all the work
StoppableMethod()
SyncLock (DirectCast(m_waitHandles, IList).SyncRoot)
waitHandle = m_waitHandles.Item(Threading.WaitHandle.WaitAny(m_waitHandles.ToArray()))
End SyncLock
waitHandle.Reset()
NonStoppableMethod()
waitHandle.Set()
SyncLock (DirectCast(m_waitHandles, IList).SyncRoot)
m_waitHandles.Remove(waitHandle)
End SyncLock
The last thing done is to modify the Stop method to be sure that the Stop operation will not done with any thread inside the NonStoppableMethod:
SyncLock (DirectCast(m_waitHandles, IList).SyncRoot)
If m_waitHandles.Count > 0 Then
Threading.WaitHandle.WaitAll(m_waitHandles.ToArray())
End If
End SyncLock
I'm not sure that this is done in a right way because it's the first time I deal with things like that. Do you feel that this is ok and is a good approach?
Thanks to all, mates!
Try using this:
int threadsCompleted = 0;
int numberOfThreads = 4;
ManualResetEvent completedEvent = new ManualResetEvent(false);
In each thread:
// Do task
if (Interlocked.Increment(threadsCompleted) == numberOfThreads)
completedEvent.Set();
Main thread:
completedEvent.WaitOne();
All over the internet people try to use an array of EventHandles
and WaitAll()
. I've come up with the following class which is much lighter on resources. I tried to think of different race scenarios and I believe there is no race condition in this code. (There is a theoretical race between decrementing and checking the condition on Count
, but as far as I can tell it does not affect functionality and the code will still always work.)
To use this class, all threads that need synchronization must call its Wait()
method. They will block until Count
number of threads have called Wait()
. A single instance can only be used to synchronize once (it cannot be reset).
internal class ThreadBarrier
{
private ManualResetEvent BarrierEvent;
private int Count;
internal ThreadBarrier(int count)
{
BarrierEvent = new ManualResetEvent(false);
Count = count;
}
internal void Wait()
{
Interlocked.Decrement(ref Count);
if (Count > 0)
BarrierEvent.WaitOne();
else
BarrierEvent.Set();
}
}
Use Thread.Join (that blocks the calling thread until a thread terminates, while continuing to perform standard COM and SendMessage pumping) method like in the example:
using System;
using System.Threading;
class IsThreadPool
{
static void Main()
{
AutoResetEvent autoEvent = new AutoResetEvent(false);
Thread regularThread =
new Thread(new ThreadStart(ThreadMethod));
regularThread.Start();
ThreadPool.QueueUserWorkItem(new WaitCallback(WorkMethod),
autoEvent);
// __________ Wait for foreground thread to end. __________
regularThread.Join();
// Wait for background thread to end.
autoEvent.WaitOne();
}
static void ThreadMethod()
{
Console.WriteLine("ThreadOne, executing ThreadMethod, " +
"is {0}from the thread pool.",
Thread.CurrentThread.IsThreadPoolThread ? "" : "not ");
}
static void WorkMethod(object stateInfo)
{
Console.WriteLine("ThreadTwo, executing WorkMethod, " +
"is {0}from the thread pool.",
Thread.CurrentThread.IsThreadPoolThread ? "" : "not ");
// Signal that this thread is finished.
((AutoResetEvent)stateInfo).Set();
}
}
精彩评论