Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The correct way to implement ThreadPool.RegisterWaitForSingleObject

I am trying to use ThreadPool.RegisterWaitForSingleObject to add a timer to a set of threads. I create 9 threads and am trying to give each of them an equal chance of operation as at the moment there seems to be a little starvation going on if I just add them to the thread pool. I am also trying to implement a manual reset event as I want all 9 threads to exit before continuing.

What is the best way to ensure that each thread in the threadpool gets an equal chance at running as the function that I am calling has a loop and it seems that each thread (or whichever one runs first) gets stuck in it and the others don't get a chance to run.

resetEvents = new ManualResetEvent[table_seats]; 
            //Spawn 9 threads
            for (int i = 0; i < table_seats; i++)
            {
                resetEvents[i] = new ManualResetEvent(false);
                //AutoResetEvent ev = new AutoResetEvent(false);
                RegisteredWaitHandle handle = ThreadPool.RegisterWaitForSingleObject(autoEvent, ObserveSeat, (object)i, 100, false);
            }

            //wait for threads to exit
            WaitHandle.WaitAll(resetEvents);

However, it doesn't matter if I use resetEvents[] or ev neither seem to work properly. Am I able to implement this or am I (probably) misunderstanding how they should work.

Thanks, R.

like image 874
flavour404 Avatar asked Sep 23 '10 01:09

flavour404


1 Answers

I would not use the RegisterWaitForSingleObject for this purpose. The patterns I am going to describe here require the Reactive Extensions download since you are using .NET v3.5.

First, to wait for all work items from the ThreadPool to complete use the CountdownEvent class. This is a lot more elegant and scalable than using multiple ManualResetEvent instances. Plus, the WaitHandle.WaitAll method is limited to 64 handles.

var finished = new CountdownEvent(1);
for (int i = 0; i < table_seats; i++)
{
  finished.AddCount();
  ThreadPool.QueueUserWorkItem(ObserveSeat);
    (state) =>
    {
      try
      {
        ObserveSeat(state);
      }
      finally
      {
        finished.Signal();
      }
    }, i);
}
finished.Signal();
finished.Wait();

Second, you could try calling Thread.Sleep(0) after several iterations of the loop to force a context switch so that the current thread yields to another. If you want a considerably more complex coordination strategy then use the Barrier class. Add another parameter to your ObserveSeat function which accepts this synchronization mechanism. You could supply it by capturing it in the lambda expression in the code above.

public void ObserveSeat(object state, Barrier barrier)
{
  barrier.AddParticipant();
  try
  {
    for (int i = 0; i < NUM_ITERATIONS; i++)
    {
      if (i % AMOUNT == 0)
      {
        // Let the other threads know we are done with this phase and wait
        // for them to catch up.
        barrier.SignalAndWait();
      }
      // Perform your work here.
    }
  }
  finally
  {
    barrier.RemoveParticipant();
  }
}

Note that although this approach would certainly prevent the starvation issue it might limit the throughput of the threads. Calling SignalAndWait too much might cause a lot of unnecessary context switching, but calling it too little might cause a lot of unnecessary waiting. You would probably have to tune AMOUNT to get the optimal balance of throughput and starvation. I suspect there might be a simple way to do the tuning dynamically.

like image 79
Brian Gideon Avatar answered Sep 18 '22 14:09

Brian Gideon