none
ReaderWriterLockSlim.EnterReadLock - threads not entering lock in the order in which they try to acquire lock RRS feed

  • Question

  • Hi

    I've been pulling my hair out until I got to this - I have my ReaderWriterLockSlim. Say it's declared during the class contructor.

    public class MyClass() { private ReaderWriterLockSlim myLock = new new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
    ConcurrentQueue<MyObj> myQueue = new ConcurrentQueue<MyObj>();
    Random myRand = new.Random();

    internal object MyObj() { public int Number {get; set;} } public void TestMethod(int nbThreads) { for (int i = 0; i < nbThreads; i++) { MyObj number = new MyObj{Number=i};
    myQueue.Enqueue(number); Task.Factory.StartNew(() => {
    Thread.Sleep(myRand.Next(1000));

    MyObj taskNumber = null;
    if (myQueue.TryDequeue(out taskNumber))
    { Console.WriteLine("Thread " + number.Number + " trying to acquire lock"); myLock.EnterWriteLock(); Console.WriteLine("Thread " + number.Number + " acquired lock"); myLock.ExitWriteLock();
    } } } } }

    If I do

    MyClass my = new MyClass();
    my.Test(100);

    A bunch of time, the output varies often I get

    Thread x trying to acquire lock
    Thread x acquired lock
    Thread x+1 trying to acquire lock
    Thread x+1 acquired lock

    in order.. but it is well possible that I get

    Thread x
    Thread x-1 acquired lock
    Thread x acquired lock

    It seems that even though the MyObj objects dequeued from the queue are in ascending order, in case somebody has already acquired the lock, if multiple other tasks try to acquire the lock, they don't necessarily acquire the lock in the order in which they called myLock.EnterWriteLock();
    Is there a way to ensure that tasks can enter the write lock in the order in which they've called EnterWriteLock?

    Thanks

    Stephan

    P.S. The example is rather academical - it might not be able to allow to reproduce the issue since sleep intervals are at random. In my actual code (that would be impossible to use for repro since it is tied to an inhouse third party system that is not accessible from outside), I know that each Task is started in order, and I ensure in-order execution by using the shared queue (to combat whichever  unpredictability remains when using TaskCreationOptions.PreferFairness). As far as I can tell (from console dumps that I'm hoping to represent effective execution order) my tasks always execute in order but I added the queue for safety (just as in the code above the queue is filled synchronously (and even with a lock around it for additional safety)). I guess I could add another lock protected variable that gives me the last executed task and thus prevent execution of the "lock acquired" code part if once the lock is acquired, another task with a higher number has already completed, but I'm just wondering if there's a way I can guarantee that whomever tries to acquire an already acquired lock can acquire it in the order it tried to acquire it.
    Monday, August 20, 2012 7:49 PM

Answers


  • Since my assumption about who can acquire the lock in which order was wrong - is there any lock primitive (I don't need tryenter, upgradeable locks, etc.) that will guarantee that lock acquisition is performed in the order threads try to acquire the lock? Finding a synchronization primitive that can guarantee this would allow to quickly fix the issue I'm having. In the end there will be several thousand users concurrently and a couple times that in terms of phones so the more lightweight the solution the better.

    First off, I'd strongly recommend looking at TPL Dataflow.  It's built-in in .NET 4.5, but downloadable for .NET 4.  This provides an entire framework built around being able to do this type of application where you have to stream results to be processed through a system with fixed rules.  It seems like an "ideal" way to handle most of this stuff.

    That being said, synchronization primitives don't really guarantee order - they're about preventing collision.  If you need guaranteed order, then you'll need to use some form of queue (such as a ConcurrentQueue<T>) to manage the items.


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Tuesday, August 21, 2012 4:15 PM
    Moderator

All replies


  • It seems that even though the MyObj objects dequeued from the queue are in ascending order, in case somebody has already acquired the lock, if multiple other tasks try to acquire the lock, they don't necessarily acquire the lock in the order in which they called myLock.EnterWriteLock();
    Is there a way to ensure that tasks can enter the write lock in the order in which they've called EnterWriteLock?

    Thanks

    Stephan

    No.  ReaderWriterLockSlim (and ReaderWriterLock) both have no guarantees of order when acquiring write locks.  Typically, if you're using a ConcurrentQueue<T>, you'd have a single consumer that would provide the appropriate ordering - so I'm not sure why you'd need a ReaderWriterLockSlim in this case...

    What are you trying to accomplish?  There may be an alternative.

     

    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Monday, August 20, 2012 8:14 PM
    Moderator
  • I do have a single consumer indeed. I'm working on a telephony project where I have call information events coming in sequentially for different phones. All events, even if they do concern different phones, need to be processed in order.

    So I have

    public class EventData
    {
    public string ConcernsPhone {get; set;}
    public object EventInformation {get; set;}
    }

    The first thing being done in processing is look up who the phone belongs to (multiple phones may belong to the same person and it is crucial that only one event is being processed per user concurrently. To make matters more complex, there is various other information concerning a user that may arrive at the same time, and it no two bits of information regarding the same user may be processed concurrently (for instance I have information about a phone's forwarding state (is it forwarded and to whom - and this is once again per phone, not per user), and I also have information about people who'd like to be informed if user's (not phone.. phone states are consolidated per user and then broadcast to all interested parties - and if nobody is interested in a user anymore, )) current status.

    So, to ensure that no two events for the same phone are processed out of sequence (or so I thought) I was using a ReaderWriterLockSlim on EventData.ConcernsPhone, and another one for the users concerned (and those locks are reused throughout the program) (in that order if phone related events are being processed.... for the "who is interested in whom" bit I only use a lock on the user itself). What do I lock on lines... suppose a user is forwarding his/her phone and at the same time he/she gets a call. So I get two different events and I need to ensure that one is fully processed before the other has begun processing (a user's status may be "forwarded", but if there's a call, that overrides the status and the user is now "on the phone".. if suppose the event informing me about an incoming call arrives first (and then the information about the phone being forwarded) but because processing time is different, I have to avoid that the status "on the phone" is being sent first and only then the status "forwarded"... it has to be "forwarded" first, followed by "on the phone"). I do consolidate statuses in common code, but if I'm out of luck I get a race condition and "forwarded" may be sent later than "on the phone" leading to a wrong status. So I must ensure that this doesn't happen and I need some kind of synchronization mechanism to ensure that.

    Since my assumption about who can acquire the lock in which order was wrong - is there any lock primitive (I don't need tryenter, upgradeable locks, etc.) that will guarantee that lock acquisition is performed in the order threads try to acquire the lock? Finding a synchronization primitive that can guarantee this would allow to quickly fix the issue I'm having. In the end there will be several thousand users concurrently and a couple times that in terms of phones so the more lightweight the solution the better.

    Tuesday, August 21, 2012 5:40 AM

  • Since my assumption about who can acquire the lock in which order was wrong - is there any lock primitive (I don't need tryenter, upgradeable locks, etc.) that will guarantee that lock acquisition is performed in the order threads try to acquire the lock? Finding a synchronization primitive that can guarantee this would allow to quickly fix the issue I'm having. In the end there will be several thousand users concurrently and a couple times that in terms of phones so the more lightweight the solution the better.

    First off, I'd strongly recommend looking at TPL Dataflow.  It's built-in in .NET 4.5, but downloadable for .NET 4.  This provides an entire framework built around being able to do this type of application where you have to stream results to be processed through a system with fixed rules.  It seems like an "ideal" way to handle most of this stuff.

    That being said, synchronization primitives don't really guarantee order - they're about preventing collision.  If you need guaranteed order, then you'll need to use some form of queue (such as a ConcurrentQueue<T>) to manage the items.


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Tuesday, August 21, 2012 4:15 PM
    Moderator
  • Stephan -- The real answer is that your locking code is working fine.  It's the output of Console.WriteLine's that appears out of order because of the multithreaded nature of your application test code.  Once a given thread has acquired the lock, it is NOT the case  -- despite what appears in your console output window -- that another thread has acquired that lock.   

    You can verify this with the [working] version below of the code you posted;  I added a console write right before the lock release, and you will never see another thread acquire the lock before you see the "lock release" message written.  Hope this is helpful, Keith

    using System;
    using System.Threading;
    using System.Collections.Concurrent;
    using System.Threading.Tasks;
    
    namespace Lockingtest
    {
        class Program
        {
            static void Main(string[] args)
            {
                var c = new MyClass();
                c.TestMethod(100);
    
                Console.WriteLine("\nDone - hit any key\n");
                Console.ReadKey();
            }
        }
    
        public class MyClass
        {
            private ReaderWriterLockSlim myLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
            ConcurrentQueue<MyObj> myQueue = new ConcurrentQueue<MyObj>();
            Random myRand = new Random((int)DateTime.Now.Ticks);
    
            internal class MyObj
            {
                public int Number { get; set; }
            }
    
            public void TestMethod(int nbThreads)
            {
                for (int i = 0; i < nbThreads; i++)
                {
                    MyObj number = new MyObj { Number = i };
                    myQueue.Enqueue(number);
                    Task.Factory.StartNew(() =>
                    {
                        Thread.Sleep(myRand.Next(500));
                        MyObj taskNumber = null;
                        if (myQueue.TryDequeue(out taskNumber))
                        {
                            Console.WriteLine("Thread " + number.Number + " trying to acquire lock");
                            myLock.EnterWriteLock();
                            Console.WriteLine("Thread " + number.Number + " acquired lock");
                            // --->>> Add this line to prove that lock is released by this thread FIRST <<<----
                            Console.WriteLine("Thread " + number.Number + " released lock");
                            myLock.ExitWriteLock();
                        }
                    }
                    );
                }
            }
    
        }
    }

     

    Thursday, November 1, 2012 7:20 PM