C#Monitor / Semaphore Concurrency Produce-Consumer for Buffer

时间:2012-02-15 04:02:17

标签: c# multithreading monitor

我正致力于解决典型的生产者 - 消费者问题。我有多个生产者和一个消费者。有 n 生产者线程,每个线程都调用 SetOne(OrderObject order),消费者调用 GetOne()缓冲区类用作包含< n个单元格,并且当缓冲区类可用时,每个单元格都被消耗掉。出于某种原因,下面的设置有时会起作用,但并不总是消耗所有单元格。我已经包含了所有涉及的类客户端,服务器和缓冲区。另外,我可以展示一个运行这个原型的简单原型。 FYI - 使用的方法是首先将信号量初始化为与正在使用的缓冲区大小相同的值,然后在缓冲区打开后,找到一个打开的单元格,然后对该单元格执行操作。

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();
    }
}

我猜我遇到了以下问题之一:死锁,活锁或饥饿。由于某种原因,服务器无法使用生成并添加到单元缓冲区的所有订单对象。不确定修复是什么。

2 个答案:

答案 0 :(得分:1)

好的,让我们分解这段代码试图做的事情。

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

//设置 One

    public static void SetOne(OrderObject order)
    {

//为什么你需要一个信号量?

        _pool.WaitOne();
        try
        {

//信号量是多余的,因为Monitor.Enter是一个限制性更强的锁。

            Monitor.Enter(BufferCells);

//嗯?我以为这应该是SetOne?不是SetEverything?我只能假设您的意图是设置其中一个单元格,而其余单元格可用于设置或获取。如果这是您要实现的目标,那么Queue似乎是更合适的数据结构。更好的是BlockingCollection还启用了锁定/阻塞机制。

            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;
    }
 }

总结一下:

  • 此代码使用冗余锁定,这是不必要的
  • 此代码在独占锁定下一举设置缓冲区中的所有单元格,这似乎首先打破了缓冲区的目的
  • 这个代码试图实现的内容似乎已在BlockingCollection中实现。无需重新发明轮子! :)
祝你好运!

答案 1 :(得分:0)

我将假设监视器是为了保护对数组的访问,并且信号量应该进行计数,是吗?

如果是这样,你不应该在getOne()的finally部分调用'_pool.Release()',你不应该在setOne()的顶部调用'_pool.WaitOne()'。生产者的工作是在信号量推到一个对象之后发出信号,并且消费者的工作是在弹出一个对象之前等待信号量。

Aarrgghh!

'使用的方法是首先将信号量初始化为与使用的缓冲区大小相同的',

如果需要无界队列,请将信号量初始化为0。

如果你想要一个有界的队列,正如你上面的文字暗示的那样,你需要两个信号量(以及监视器), - 一个,'我',计算队列中的项目,一个,'S ',计算队列中剩余的空格。将I初始化为0,将S初始化为队列大小。在制片人中,等待S,锁定,推动,解锁,发出信号I.在消费者中,等待我,锁定,弹出,解锁,发出信号S.