C# Producer-Consumer using Semaphores

不羁岁月 提交于 2021-02-11 10:24:27

问题


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 WaitHandles 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!