Monitor VS Lock

Monitor vs lock

Eric Lippert talks about this in his blog:
Locks and exceptions do not mix

The equivalent code differs between C# 4.0 and earlier versions.


In C# 4.0 it is:

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

It relies on Monitor.Enter atomically setting the flag when the lock is taken.


And earlier it was:

var temp = obj;
Monitor.Enter(temp);
try
{
body
}
finally
{
Monitor.Exit(temp);
}

This relies on no exception being thrown between Monitor.Enter and the try. I think in debug code this condition was violated because the compiler inserted a NOP between them and thus made thread abortion between those possible.

In Java, what is the difference between a monitor and a lock

From the official documentation of Locks and Synchronization:

  • Synchronization is built around an internal entity known as the intrinsic lock or monitor lock. (The API specification often refers to this entity simply as a "monitor.")
  • Every object has an intrinsic lock associated with it. By convention, a thread has to acquire the object's monitor lock
    before accessing them, and then release the monitor lock when it's
    done with them. A thread is said to own the lock between the time it
    has acquired the lock and released the lock. As long as a thread owns
    a monitor lock, no other thread can acquire the same lock. The other
    thread will block when it attempts to acquire the lock.
  • When a thread releases the lock, a happens-before relationship is established between that action and any subsequent acquisition of the
    same lock.

So a monitor and a lock can not be compared for differences, rather they are complementary to each other. Every object in Java is associated with a monitor which a thread can lock or unlock.

Difference Between Monitor & Lock?

For example in C# .NET a lock statement is equivalent to:

Monitor.Enter(object);
try
{
// Your code here...
}
finally
{
Monitor.Exit(object);
}

However, keep in mind that Monitor can also Wait() and Pulse(), which are often useful in complex multithreading situations.

Edit:
In later versions of the .NET framework, this was changed to:

bool lockTaken = false;
try
{
Monitor.Enter(object, ref lockTaken);
// Your code here...
}
finally
{
if (lockTaken)
{
Monitor.Exit(object);
}
}

Monitor vs Mutex

Since you haven't specified which OS or language/library you are talking about, let me answer in a generic way.

Conceptually they are the same. But usually they are implemented slightly differently

Monitor

Usually, the implementation of monitors is faster/light-weight, since it is designed for multi-threaded synchronization within the same process. Also, usually, it is provided by a framework/library itself (as opposed to requesting the OS).

Mutex

Usually, mutexes are provided by the OS kernel and libraries/frameworks simply provide an interface to invoke it. This makes them heavy-weight/slower, but they work across threads on different processes. OS might also provide features to access the mutex by name for easy sharing between instances of separate executables (as opposed to using a handle that can be used by fork only).

Does Monitor.TryEnter and lock() work together?

lock will block until the resource is available

TryEnter will not do anything if it is already locked.

Depending on your needs you have to use one or the other.

In your case f2() will always do what ever it does no matter how long it takes. f1() will return immediately if there is lock contention

Using lock, Monitor Pulse and Wait to synchronize threads

Here is a relatively simple Wait/PulseAll example:

object locker = new();
int i = 0;
bool finished = false;
Thread[] threads = Enumerable.Range(0, 3).Select(remainder => new Thread(() =>
{
lock (locker)
{
try
{
do
{
while (!finished && i % 3 != remainder) Monitor.Wait(locker);
if (finished) break;
Console.WriteLine($"Worker #{remainder} produced {i}");
Monitor.PulseAll(locker);
} while (++i < 20);
}
finally { finished = true; Monitor.PulseAll(locker); }
}
})).ToArray();
Array.ForEach(threads, t => t.Start());
Array.ForEach(threads, t => t.Join());

Three worker threads are created, identified by the remainder argument, that takes the values 0, 1, and 2. Each worker is responsible for producing numbers whose module 3 equals the remainder.

The int i is the loop variable, and the bool finished is a flag that becomes true when any worker is finished. This flag ensures that in case of an error in any worker, the other workers will not deadlock.

Each worker enters a critical section that encloses a do-while loop, which is the number-producing and incrementing loop. Before emitting a number it has to wait for its turn. Its turn comes when the i % 3 == remainder. Otherwise it Waits. When its turn comes, it emits the number, it increments the i, it Pulses all waiting workers, and continues with the next iteration. When the loop ends, it Pulses one last time before releasing the lock.

The PulseAll has been chosen instead of the Pulse, because we don't know whether the next worker in the waiting queue is the correct one for the current i, so we just wake them all.

Output:

Worker #0 produced 0
Worker #1 produced 1
Worker #2 produced 2
Worker #0 produced 3
Worker #1 produced 4
Worker #2 produced 5
Worker #0 produced 6
Worker #1 produced 7
Worker #2 produced 8
Worker #0 produced 9
Worker #1 produced 10
Worker #2 produced 11
Worker #0 produced 12
Worker #1 produced 13
Worker #2 produced 14
Worker #0 produced 15
Worker #1 produced 16
Worker #2 produced 17
Worker #0 produced 18
Worker #1 produced 19

Try it on fiddle.


Note: the example in the 1st revision of this answer was problematic, because it was creating an initial busy-wait phase until all workers were ready.



Related Topics



Leave a reply



Submit