Reputation: 12323
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
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
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
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
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