问题
Inspired by The Little Book of Semaphores, I decided to implement the Producer-Consumer problem using Semaphores.
I specifically want to be able to stop all Worker threads at will. I've tested my methodolodgy extensively and can't find anything faulty.
Following code is a prototype for testing and can be ran as a Console application:
using System;
using System.Collections.Concurrent;
using System.Threading;
using NUnit.Framework;
public class ProducerConsumer
{
private static readonly int _numThreads = 5;
private static readonly int _numItemsEnqueued = 10;
private static readonly Semaphore _workItems = new Semaphore(0, int.MaxValue);
private static readonly ManualResetEvent _stop = new ManualResetEvent(false);
private static ConcurrentQueue<int> _queue;
public static void Main()
{
_queue = new ConcurrentQueue<int>();
// Create and start threads.
for (int i = 1; i <= _numThreads; i++)
{
Thread t = new Thread(new ParameterizedThreadStart(Worker));
// Start the thread, passing the number.
t.Start(i);
}
// Wait for half a second, to allow all the
// threads to start and to block on the semaphore.
Thread.Sleep(500);
Console.WriteLine(string.Format("Main thread adds {0} items to the queue and calls Release() {0} times.", _numItemsEnqueued));
for (int i = 1; i <= _numItemsEnqueued; i++)
{
Console.WriteLine("Waking up a worker thread.");
_queue.Enqueue(i);
_workItems.Release(); //wake up 1 worker
Thread.Sleep(2000); //sleep 2 sec so it's clear the threads get unblocked 1 by 1
}
// sleep for 5 seconds to allow threads to exit
Thread.Sleep(5000);
Assert.True(_queue.Count == 0);
Console.WriteLine("Main thread stops all threads.");
_stop.Set();
// wait a while to exit
Thread.Sleep(5000);
Console.WriteLine("Main thread exits.");
Console.WriteLine(string.Format("Last value of Semaphore was {0}.", _workItems.Release()));
Assert.True(_queue.Count == 0);
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
}
private static void Worker(object num)
{
// Each worker thread begins by requesting the semaphore.
Console.WriteLine("Thread {0} begins and waits for the semaphore.", num);
WaitHandle[] wait = { _workItems, _stop };
int signal;
while (0 == (signal = WaitHandle.WaitAny(wait)))
{
Console.WriteLine("Thread {0} becomes unblocked by Release() and has work to do.", num);
int res;
if (_queue.TryDequeue(out res))
{
Console.WriteLine("Thread {0} dequeues {1}.", num, res);
}
else
{
throw new Exception("this should not happen.");
}
}
if (signal == 1)
Console.WriteLine("Thread {0} was stopped.", num);
Console.WriteLine("Thread {0} exits.", num);
}
}
Now for my question, I'm using WaitHandle.WaitAny(semaphore)
under the assumption that when I call Release()
on the semaphore, only 1 Worker will be woken up. However, I can't find reassurance in the documentation that this is actually true. Can anyone confirm this is true?
回答1:
It is indeed interesting that it seems that the documentation doesn't state explicitly that in the case of WaitOne
only 1 thread will receive the signal. When you get familiar with multithreading theory this becomes somewhat self-evident.
Yes, WaitOne
that is called on Semaphore
(and WaitAny
that is called on a list of WaitHandle
s that include Semaphore
) is received by a single thread. If you want reference from MSDN so here it is, Semaphore
is child class of WaitHandle
, which is:
Encapsulates operating system–specific objects that wait for exclusive access to shared resources.
So yes, unless explicitly stated methods provide exclusive access.
For example method WaitOne
of ManualResetEvent
will unblock for all waiting threads, but documentation is explicit about it:
Notifies one or more waiting threads that an event has occurred.
来源:https://stackoverflow.com/questions/43048699/c-sharp-producer-consumer-using-semaphores