简体   繁体   中英

Multithreading and Locking (Thread-Safe operations)

So I have a class with a few methods which all use locking in order to prevent weird things happening when someone uses an instance of my class with multiple threads accessing it:

public class SomeRandomClass
{
    private object locker = new object();

    public void MethodA()
    {
        lock (locker)
        {
            // Does something
            MethodB();
        }
    }

    public void MethodB()
    {
        lock (locker)
        {
            // Does something else
        }
    }
}

As we can see, MethodB() is automatically accessed by MethodA() , but that won't work since MethodA() has currently locked the locker object.

I want to make MethodB() accessible publicly, so you can call it manually whenever needed, but I do NOT want it to be used while MethodA() is doing things (that's why I'm using a locker object).

And of course I do not want MethodA() to do things while MethodB() is doing stuff. I basically want only one of all the methods to be used at the same time, but MethodA() needs to access MethodB() somehow without removing the lock (so that it stays completely thread-safe the whole time).

I really hope it is kind of understandable what I'm trying to ask... If there's any questions about my question, then please go ahead and post them below. Answers/Solutions are very much appreciated as well!

The solution is probably incredibly easy and I'm just not seeing it.

By the way, the above is supposed to be C#-code.

An easy solution would be to create a private method that contains what MethodB does that can be called by MethodA and another public MethodB

The private MethodB does not lock, only the public ones do.

For example:

public class SomeRandomClass {

    private object locker = new object();

    public void MethodA {
        lock(locker) {
            // exclusive club
            // do something before calling _methodB
            _methodB();
        }
    }
    private void _methodB {
        // do that, what used to be done by MethodB
    }
    public void MethodB {
        //this one only exists to expose _methodB in a thread-safe context
        lock(locker) {      
            _methodB();
        }
    }
}

PS

I think it is obvious to you and everyone else why your code is somewhat designed to create a deadlock.


Update :

Apparently lock(object) {} is re-entrant as pointed out in the comments, so the obvious deadlock isn't even one.

Locking forbids what you're trying to do -- that's its purpose.

One thing to do here is creating a private method that you can access from both methodA and methodB . That method wouldn't use locking, and wouldn't be thread safe, but could be called from either one of the locking methods.

You have race condition here: it make data incorrect. I suppose method A write static theVar variable of type string:

thread A -> call method A -> lock -> change theVar to "A"
thread B -> call method B -> wait because thread A keep lock
thread A -> release lock to call method B
    The bug here: thread B process theVar of "A"
    If method B only read theVar, it's Ok.

Your lock mechanism needs to allow locks to be taken in a recursive way (by the same thread only), usually called reentrant . lock ( Monitor class internally).

It is legal for the same thread to invoke Enter more than once without it blocking; however, an equal number of Exit calls must be invoked before other threads waiting on the object will unblock.

See also Recursive / nested locking in C# with the lock statement and Re-entrant locks in C#

As pointed out by Henk Holterman in the comment, the Monitor class is already reentrant. And the lock statement is managing the right amount of Enter and Exit calls to the underlying Monitor class.

The ReaderWriterLockSlim class is an example for a lock mechanism where one can choose between reentrant and non-reentrant . See https://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim(v=vs.110).aspx

var rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

Replace your lock { ... } with

ReaderWriterLockSlim rwLock = 
    new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

...

try 
{
    rwLock.EnterWriteLock();
    // Does something
}    
finally 
{
    rwLock.ExitWriteLock();
}

```

The Code written by you is correct.

Because according to Microsoft, once the call is acquired even if program calls for lock in the same flow, it will not be blocked as lock is already with the thread. The code works as below.

  1. call "MethodA" -->acquire lock --> call "MethodB" (will not be blocked as thread is already acquired lock) and execution will be completed.

  2. Call "MethodB" in between previous execution from another thread, the execution will be blocked as lock is with first thread.

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