Non blocking locking

前端 未结 4 1554
悲&欢浪女
悲&欢浪女 2021-01-04 19:28

I want to start some new threads each for one repeating operation. But when such an operation is already in progress, I want to discard the current task. In my scenario I ne

相关标签:
4条回答
  • 2021-01-04 20:07

    You might find some useful information on the following site (the PDf is also downlaodable - recently downloaded it myself). The Adavnced threading Suspend and Resume or Aborting chapters maybe what you are inetrested in.

    0 讨论(0)
  • 2021-01-04 20:08

    One option would be to work with a reentrancy sentinel:

    You could define an int field (initialize with 0) and update it via Interlocked.Increment on entering the method and only proceed if it is 1. At the end just do a Interlocked.Decrement.

    Another option:

    From your description it seems that you have a Producer-Consumer-Scenario...

    For this case it might be helpful to use something like BlockingCollection as it is thread-safe and mostly lock-free...

    Another option would be to use ConcurrentQueue or ConcurrentStack...

    0 讨论(0)
  • 2021-01-04 20:11

    You should use Interlocked class atomic operations - for best performance - since you won't actually use system-level sychronizations(any "standard" primitive needs it, and involve system call overhead). //simple non-reentrant mutex without ownership, easy to remake to support //these features(just set owner after acquiring lock(compare Thread reference with Thread.CurrentThread for example), and check for matching identity, add counter for reentrancy) //can't use bool because it's not supported by CompareExchange private int lock;

    public bool TryLock()
    {
      //if (Interlocked.Increment(ref _inUseCount) == 1)      
      //that kind of code is buggy - since counter can change between increment return and
      //condition check - increment is atomic, this if - isn't.       
      //Use CompareExchange instead
      //checks if 0 then changes to 1 atomically, returns original value
      //return true if thread succesfully occupied lock
      return CompareExchange(ref lock, 1, 0)==0;
      return false;
    
    }
    public bool Release()
    {
      //returns true if lock was occupied; false if it was free already
      return CompareExchange(ref lock, 0, 1)==1;
    }
    
    0 讨论(0)
  • 2021-01-04 20:24

    The lock(someObject) statement, which you may have come across, is syntactic sugar around Monitor.Enter and Monitor.Exit.

    However, if you use the monitor in this more verbose way, you can also use Monitor.TryEnter which allows you to check if you'll be able to get the lock - hence checking if someone else already has it and is executing code.

    So instead of this:

    var lockObject = new object(); 
    
    lock(lockObject)
    {
        // do some stuff
    }
    

    try this (option 1):

    int _alreadyBeingExecutedCounter;
    var lockObject = new object();
    
    if (Monitor.TryEnter(lockObject))
    {
       // you'll only end up here if you got the lock when you tried to get it - otherwise you'll never execute this code.
    
        // do some stuff
    
        //call exit to release the lock
        Monitor.Exit(lockObject);
    }
    else
    {
        // didn't get the lock - someone else was executing the code above - so I don't need to do any work!
       Interlocked.Increment(ref _alreadyBeingExecutedCounter);
    }
    

    (you'll probably want to put a try..finally in there to ensure the lock is released)

    or dispense with the explicit lock althogether and do this

    (option 2)

    private int _inUseCount;
    
    public void MyMethod()
    {
        if (Interlocked.Increment(ref _inUseCount) == 1)
        {
            // do dome stuff    
        }
        Interlocked.Decrement(ref _inUseCount);
    }
    

    [Edit: in response to your question about this]

    No - don't use this to lock on. Create a privately scoped object to act as your lock.

    Otherwise you have this potential problem:

    public class MyClassWithLockInside
    {
        public void MethodThatTakesLock()
        {
            lock(this)
            {
                // do some work
            }
        }
     }
    
    public class Consumer
    {
        private static MyClassWithLockInside _instance = new MyClassWithLockInside();
    
        public void ThreadACallsThis()
        {
              lock(_instance)
              {
                  // Having taken a lock on our instance of MyClassWithLockInside,
                  // do something long running
                  Thread.Sleep(6000);
               }
        }
    
        public void ThreadBCallsThis()
        {
             // If thread B calls this while thread A is still inside the lock above,
             // this method will block as it tries to get a lock on the same object
             // ["this" inside the class = _instance outside]
             _instance.MethodThatTakesLock();
        }  
    }
    

    In the above example, some external code has managed to disrupt the internal locking of our class just by taking out a lock on something that was externally accessible.

    Much better to create a private object that you control, and that no-one outside your class has access to, to avoid these sort of problems; this includes not using this or the type itself typeof(MyClassWithLockInside) for locking.

    0 讨论(0)
提交回复
热议问题