C# Monitor/Semaphore Concurrency Produce-Consumer for Buffer

自作多情 提交于 2021-01-29 17:05:00

问题


I am working on solving a problem with the typical producer-consumer problem. I have multiple producers, and one consumer. There are n producer threads that each call SetOne(OrderObject order) and the consumer calls GetOne(). The Buffer class is used as the buffer which contains < n cells and are each cell are consumed when available by the Buffer class. For some reason, the setup below is working sometimes, but is not always consuming all of the cells. I have included all the classes that are involved Client,Server and Buffer. Additionally, I can show a simple prototype that is running this prototype. FYI - The method being used is to first initialize the amount of semaphores to the same size of the buffer being used, then once a buffer opens, to find an open cell and then perform an operation on that cell.

public class Buffer
{
    public static OrderObject[] BufferCells;
    public static Semaphore _pool { get; set; }
    public static void SetOne(OrderObject order)
    {

        _pool.WaitOne();
        try
        {
            Monitor.Enter(BufferCells);
            for (int i = 0; i < BufferCells.Length - 1; i++)
            {
                BufferCells[i] = order;
                Console.WriteLine(String.Format("Client {0} Produced {1}", BufferCells[i].Id, BufferCells[i].Id));
            }
        }
        finally
        {
            Monitor.Exit(BufferCells);
            _pool.Release();
        } 
    }

    public static OrderObject GetOne()
    {
        _pool.WaitOne();
        OrderObject value = null;
        try
        {
            Monitor.Enter(BufferCells);
            for (int i = 0; i < BufferCells.Length - 1; i++)
            {
                if (BufferCells[i].Id != "-1")
                {
                    value = BufferCells[i];
                    BufferCells[i] = new OrderObject() { Id = "-1" }; /*Clear Cell*/
                    Console.WriteLine(String.Format("        Server Consumed {0}", value.Id));
                    break;
                }
            }
        }
        finally
        {
            Monitor.Exit(BufferCells);
            _pool.Release();
        }
        return value;
    }
 }


public class Client
{
    public int Id {get;set;}

    public void Run()
    {
         /*Produce*/
         Buffer.SetOne(Id);

    }
}

public class Server
{
    public void Run()
    {
        while(true)
        {
             Buffer.GetOne();
        }
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        /*Initialize 2 Open Semaphores*/
        int numCells = 2;
        Semaphore pool = new Semaphore(numCells, numCells);

        /*Initialize BufferCells with Empty OrderObjects*/
        List<OrderObject> OrderObjects = new List<OrderObject>();
        for (var i = 0; i < numCells; i++)
        {
            OrderObjects.Add(new OrderObject() { Id = "-1" });
        }
        Buffer.BufferCells = OrderObjects.ToArray();

        /*Initialize Consumer Thread*/
        Server server = new Server(pool);
        Thread serverThread = new Thread(new ThreadStart(server.Run));


        /*Initialize Producer Objects*/
        List<Client> clients = new List<Client>();
        for (int i = 0; i <= 20; i++)
        {
            /*Create 5000 Clients*/
            Client client = new Client(i.ToString(), pool, new OrderObject() { Id = i.ToString() });
            clients.Add(client);
        }

        /*Start Each Client Thread*/
        List<Thread> clientThreads = new List<Thread>();
        foreach (var client in clients)
        {
            Thread t = new Thread(new ThreadStart(client.Run));
            clientThreads.Add(t);
        }

        /*Start Server Thread*/
        serverThread.Start();

        /*Start Each Producer Thread*/
        clientThreads.ForEach(p => p.Start());

        /*Start Consumer Thread*/
        Console.ReadLine();
    }
}

I am guessing that I am running into one of the following problems: Deadlock,Livelock or starvation. For some reason Server is failing to consume all of the order object(s) produced and added to the cell buffer. Not sure what the fix is.


回答1:


Ok, let's decompose what this code is trying to do..

public class Buffer
{
    public static OrderObject[] BufferCells;
    public static Semaphore _pool { get; set; }

// Set One

    public static void SetOne(OrderObject order)
    {

// Why do you need a semaphore here?

        _pool.WaitOne();
        try
        {

// Semaphore, is redundant since the Monitor.Enter is a more restrictive lock.

            Monitor.Enter(BufferCells);

// Huh? I thought this was supposed to be SetOne? Not SetEverything? I can only assume that your intention was to set one of the cells while let the rest be available for setting or getting. A Queue seems like a more appropriate data structure if this is what you are trying to achieve. Better yet a BlockingCollection that also has locking/blocking mechanics enabled.

            for (int i = 0; i < BufferCells.Length - 1; i++)
            {

                BufferCells[i] = order;
                Console.WriteLine(String.Format("Client {0} Produced {1}", BufferCells[i].Id, BufferCells[i].Id));
            }
        }
        finally
        {
            Monitor.Exit(BufferCells);
            _pool.Release();
        } 
    }

    public static OrderObject GetOne()
    {

// Again this semaphore does not seem to be very helpful here

        _pool.WaitOne();
        OrderObject value = null;
        try
        {

// Because again the monitor is a more restrictive lock

            Monitor.Enter(BufferCells);
            for (int i = 0; i < BufferCells.Length - 1; i++)
            {
                if (BufferCells[i].Id != "-1")
                {
                    value = BufferCells[i];
                    BufferCells[i] = new OrderObject() { Id = "-1" }; /*Clear Cell*/
                    Console.WriteLine(String.Format("        Server Consumed {0}", value.Id));
                    break;
                }
            }
        }
        finally
        {
            Monitor.Exit(BufferCells);
            _pool.Release();
        }
        return value;
    }
 }

To sum up:

  • This code makes use of redundant locking, which is unnecessary
  • This code sets all the cells in a buffer in one swoop under an exclusive lock, which seems to defeat the purpose of the buffer in the first place
  • It seems like what this code is trying to achieve is already implemented in BlockingCollection. No need to reinvent the wheel! :)

Good luck!




回答2:


I'm going to assume that the monitor is to protect access to the array and the semaphore is supposed to do the counting, yes?

If so, you should not call '_pool.Release()' in the finally section of getOne() and you should not call '_pool.WaitOne()' at the top of setOne(). It is the producers's job to signal the semaphore after it has pushed on an object and the consumers's job to wait on the semaphore before popping an object.

Aarrgghh!

'The method being used is to first initialize the amount of semaphores to the same size of the buffer being used,'

If you want an unbounded queue, initialize the semaphore to 0.

If you want a bounded queue, as hinted at by your text above, you need two semaphores, (as well as the monitor), - one, 'I', to count the items in the queue, one, 'S', to count the spaces left in the queue. Initialize I to 0 and S to the queue size. In the producer, wait on S, lock, push, unlock, signal I. In the consumer, wait on I, lock, pop, unlock, signal S.



来源:https://stackoverflow.com/questions/9287701/c-sharp-monitor-semaphore-concurrency-produce-consumer-for-buffer

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