9

I thought that the following code would let all the 10 threads run, two at a time, and then print "done" after Release() is called 10 times. But that's not what happened:

        int count = 0;

        Semaphore s = new Semaphore(2, 2);
        for (int x = 0; x < 10; x++)
        {
            Thread t = new Thread(new ThreadStart(delegate()
            {
                s.WaitOne();
                Thread.Sleep(1000);
                Interlocked.Increment(ref count);                       
                s.Release();
            }));
            t.Start(x);
        }

        WaitHandle.WaitAll(new WaitHandle[] { s });
        Console.WriteLine("done: {0}", count);

output:

done: 6

If the only way to implement the functionality I'm looking for is to pass an EventWaitHandle to each thread and then do a WaitAll() on an array of those EventWaitHandles, then what's the meaning of doing a WaitAll() on an array of only a semaphore? In other words, when does the waiting thread unblock?

John Smith
  • 4,416
  • 7
  • 41
  • 56
  • It's very possible you are seeing a localized race condition; `Console.WriteLine` has it's own locks around it and the order in that the content is output is not guaranteed to be the order in which you call it. You might want to write tick values or use a logging framework that handles this case better. The output doesn't show that two threads *aren't* running at the same time two-at-a-time. – casperOne Jan 25 '13 at 20:13
  • 2
    Just FYI, you may want to consider using `Interlocked.Increment` instead of `volatile`: http://stackoverflow.com/questions/154551/volatile-vs-interlocked-vs-lock – Chris Sinclair Jan 25 '13 at 20:15
  • casperOne, I'm not sure if that's correct, because I experience the same problem even if I replace the last line with System.Diagnostics.Debug.WriteLine("done"); – John Smith Jan 25 '13 at 20:33
  • Im not really sure what the point of this example is - You're starting 10 threads anyways. If you want to run two threads at a time, why not just open only two threads and devide the work evenly. – caesay Jan 25 '13 at 20:41
  • If you want to wait for all threads to finish, keep the `Thread` objects around and call `Join` on each one in turn. – Jon Jan 25 '13 at 21:01

2 Answers2

6

WaitHandle.WaitAll just waits until all the handlers are in signalled state.

So when you call WaitHandle.WaitAll on one WaitHandle it works the same as you call s.WaitOne()

You can use, for example, the following code to wait for all the started threads, but allow two threads to run in parallel:

int count = 0;

Semaphore s = new Semaphore(2, 2);
AutoResetEvent[] waitHandles = new AutoResetEvent[10];
for (int x = 0; x < 10; x++)
    waitHandles[x] = new AutoResetEvent(false);

for (int x = 0; x < 10; x++)
{
    Thread t = new Thread(threadNumber =>
        {
            s.WaitOne();
            Thread.Sleep(1000);
            Interlocked.Increment(ref count);
            waitHandles[(int)threadNumber].Set();
            s.Release();
        });
    t.Start(x);
}

WaitHandle.WaitAll(waitHandles);
Console.WriteLine("done: {0}", count);
Nick Hill
  • 4,867
  • 3
  • 23
  • 29
1

WaitHandle.WaitAll(new WaitHandle[] { s }); waits just like s.WaitOne();. It enters at the first opportunity. You seem to expect this call to wait for all other semaphore operations but there is no way the operating system can tell the difference. This command might well be the first that is granted access to the semaphore.

I think what you need is the Barrier class. It is made for fork-join-style parallelism.

usr
  • 168,620
  • 35
  • 240
  • 369
  • @caesay the Barrier class surely has lots of tutorials available on the web. It is easy to use. – usr Jan 25 '13 at 21:12