Sven van den Boogaart
Sven van den Boogaart

Reputation: 12323

Lock what happens to passed object?

in a project im playing around with threads. Im trying to make a safe thread that dosn't 'corrupt' the data. My thread runs in the background and call functions on an other class, I can call Go and Go2, one function adds and one deletes from a list. I dont want them to run at the same time, what is the difference between the following situation:

static readonly object _locker1 = new object();
static readonly object _locker2 = new object();


public void Go(Object something)
{
  lock (_locker1)
  {
    myList.add(something);
  }
}

public void Go2(Object something)
{
  lock (_locker2)
  {
    myList.Remove(something);
  }
}

And if i would replace Go2 with:

public void Go2(Object something)
{
  lock (_locker1)
  {
    myList.Remove(something);
  }
}

Note the lock parameter.

A third situation that would help me understand, lets say i call Go from a different thread (thread2), can it run because _locker1 is locked by thread2 and Go2 (which has _locker 1 that is locked by thread2) is called from thread1?

static readonly object _locker1 = new object();
static readonly object _locker2 = new object();


public void Go(Object something)
{
  lock (_locker1)
  {
    //Can I call Go2 which is locked by the same object?
    Go2(something);
  }
}

public void Go2(Object something)
{
  lock (_locker1)
  {
    myList.Remove(something);
  }
}

Could someone explain what the value passed to lock does?

Upvotes: 2

Views: 505

Answers (4)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476547

As the documentation says:

The lock keyword marks a statement block as a critical section by obtaining the mutual-exclusion lock for a given object, executing a statement, and then releasing the lock.

lock is thus syntactical sugar for a monitor for the given object. When you use your first code example, if both threads execute Go (or Go2) they will have to wait for each other whereas if the two perform a different method, the can result in synchronization conflicts.

In the second code example, since you always lock an unlock the same object, it is guaranteed that no myList.add and myList.remove can be performed at the same time.

EDIT: in case of the third situation, recursive locks are counted. This means that in case the first thread calls Go and the second thread calls Go2, if the first thread enters the lock first, it will do a call to Go2 get access to the lock, remove the item, return from the recursive call, leave the lock, and only then the second thread can enter the lock of Go2. In case the second thread wins the race, the second thread will first enter the lock and blocking the first thread from the outer lock. Only when the second thread leaves the lock of Go2, the first thread can enter the lock of Go (and perform the recursive call).

Upvotes: 2

MattTannahill
MattTannahill

Reputation: 616

The value passed into lock is symbolic of the shared state that will be accessed in the block (curly braces) the the lock statement. Each request for a lock on that value will be queued and processed in order. Only one requestor for that value is allowed to process at a time, so the shared state is only ever accessed by one requestor at a time.

Abstraction

It's like if I had a meeting with a rubber chicken, and I said "only the person holding the rubber chicken can speak". In this scenario, the rubber chicken is the lock parameter, and the ability to speak is the shared resource. Everyone wishing to speak will form a line. Only one person can hold the chicken, so only one person can speak. When the person speaking is done, they hand the chicken to the next person in line.

In your first situation, you have two rubber chickens: locker1 (rubber chicken 1) and locker2 (rubber chicken 2). Thus, Go and Go2 don't wait on each other for a turn (they both have a chicken!). A thread calling Go will be able to add to myList while another thread calling Go2 can simultaneouly access the myList to remove the item from the list. However, two threads calling to Go will have wait their turn because they both require the same rubber chicken: locker1; the same goes for two threads calling Go2.

If you make both Go and Go2 use the same value (the same chicken), then they would have to wait their turn to acquire the lock on that value. This would prevent them one thread calling Go and a second thread calling Go2 from accessing myList at the same time.

Upvotes: 1

TheDaveJay
TheDaveJay

Reputation: 783

A lock statement actually translates to a monitor lock under the hood, such as:

bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    // body
}
finally
{
    if (lockWasTaken)
    {
        Monitor.Exit(temp); 
    }
}

To find out more about how Monitor locks work, look here: MSDN Documentation

To Quote MSDN:

Use Enter to acquire the Monitoron the object passed as the parameter. If another thread has executed an Enter on the object but has not yet executed the corresponding Exit, the current thread will block until the other thread releases the object. 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. Use Monitor to lock objects (that is, reference types), not value types. When you pass a value type variable to Enter, it is boxed as an object. If you pass the same variable to Enter again, it is boxed as a separate object, and the thread does not block. In this case, the code that Monitor is supposedly protecting is not protected. Furthermore, when you pass the variable to Exit, still another separate object is created. Because the object passed to Exit is different from the object passed to Enter, Monitor throws SynchronizationLockException. For more information, see the conceptual topic Monitors.

This means that your code will be blocked on 2 different objects, where you would only want to block on one to make it thread safe.

Upvotes: 1

Cecilio Pardo
Cecilio Pardo

Reputation: 1717

It's very simple: If two locks use the same object, they will not run at the same time. In your first snippet, as Go and Go2 are locking on different objects, they could run at the same time and do bad things.

Upvotes: 6

Related Questions