简体   繁体   中英

Release a lock before waiting, and re-acquire it after

In Java, you can associate multiple Condition objects to a single ReentrantLock . What would the C# equivalent be?

Real-world example: The example implementation in the Java Condition documentation uses two Condition objects, notFull and notEmpty , tied to the same lock. How could that example be translated to C#?

Background : I often find Java code using two Condition objects to signal various states, associated to the same Lock ; in C#, it seems that you can either

  • call Monitor.Enter on an object, and then Monitor.WaitOne / Monitor.Pulse , but that's just one condition.
  • use multiple Auto/ManualResetEvent objects, but these cannot atomically reacquire a given lock after waiting.

Note : I can think of one way: using Monitor.WaitOne / Monitor.PulseAll on a single object, and checking for the condition after waking up; that's what you do in Java as well to protect against spurious wake-ups. It doesn't really do, though, because it forces you to call PulseAll instead of Pulse , since Pulse might wake up a thread waiting on another condition. Unfortunately, using PulseAll instead of Pulse has performance implications (threads competing for the same lock).

I think if you are doing new development and can do .NET 4 or above, you'll be better served by the new concurrent collection classes, like ConcurrentQueue .

But if you can't make that move, and to strictly answer your question, in .NET this is somewhat simplified imho, to implement a prod/cons pattern you would just do wait and then pulse like below (note that I typed this on notepad)

// max is 1000 items in queue
private int _count = 1000;

private Queue<string> _myQueue = new Queue<string>();

private static object _door = new object();

public void AddItem(string someItem)
{
    lock (_door)
    {
        while (_myQueue.Count == _count)
        {
            // reached max item, let's wait 'till there is room
            Monitor.Wait(_door);
        }

        _myQueue.Enqueue(someItem);
        // signal so if there are therads waiting for items to be inserted are waken up
        // one at a time, so they don't try to dequeue items that are not there
        Monitor.Pulse(_door);
    }
}

public string RemoveItem()
{
    string item = null;

    lock (_door)
    {
        while (_myQueue.Count == 0)
        {
            // no items in queue, wait 'till there are items
            Monitor.Wait(_door);
        }

        item = _myQueue.Dequeue();
        // signal we've taken something out
        // so if there are threads waiting, will be waken up one at a time so we don't overfill our queue
        Monitor.Pulse(_door);
    }

    return item;
}

Update: To clear up any confusion, note that Monitor.Wait releases a lock, therefore you won't get a deadlock

@Jason If the queue is full and you wake only ONE thread, you are not guaranteed that thread is a consumer. It might be a producer and you get stuck.

I haven't come across much C# code that would want to share state within a lock. Without rolling your own you could use a SemaphoreSlim (but I recommend ConcurrentQueue(T) or BlockingCollection(T) ).

public class BoundedBuffer<T>
{
    private readonly SemaphoreSlim _locker = new SemaphoreSlim(1,1);
    private readonly int _maxCount = 1000;
    private readonly Queue<T> _items;

    public int Count { get { return _items.Count; } }

    public BoundedBuffer()
    {
        _items = new Queue<T>(_maxCount);
    }

    public BoundedBuffer(int maxCount)
    {
        _maxCount = maxCount;
        _items = new Queue<T>(_maxCount);
    }

    public void Put(T item, CancellationToken token)
    {
        _locker.Wait(token);

        try
        {
            while(_maxCount == _items.Count)
            {
                _locker.Release();
                Thread.SpinWait(1000);
                _locker.Wait(token);
            }

            _items.Enqueue(item);
        }
        catch(OperationCanceledException)
        {
            try
            {
                _locker.Release();
            }
            catch(SemaphoreFullException) { }

            throw;
        }
        finally
        {
            if(!token.IsCancellationRequested)
            {
                _locker.Release();
            }
        }
    }

    public T Take(CancellationToken token)
    {
        _locker.Wait(token);

        try
        {
            while(0 == _items.Count)
            {
                _locker.Release();
                Thread.SpinWait(1000);
                _locker.Wait(token);
            }

            return _items.Dequeue();
        }
        catch(OperationCanceledException)
        {
            try
            {
                _locker.Release();
            }
            catch(SemaphoreFullException) { }

            throw;
        }
        finally
        {
            if(!token.IsCancellationRequested)
            {
                _locker.Release();
            }
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM