-1

To call Monitor.Wait(obj) or Monitor.Pulse(obj) it's necessary first to have entered the monitor, via lock(obj) or Monitor.Enter(obj). Why does the API require this?

Thread synchronization: Wait and Pulse demystified hints at something about conditional variables etc., but I don't see why I'd want that to always be used.

I mean if it's just that the implementation depends on the lock being acquired to do the work of Wait or Pulse, why not just implement that internally as a part of them? Why not decouple Wait and Pulse entirely from the other behaviour in Monitor and let the developer use these functions as they want? It seems like a pointless burden on the developer, but is there a good reason for it to be like this?

To be more specific, Monitor.Wait(obj) throws SynchronizationLockException if "Wait is not invoked from within a synchronized block of code". Monitor.Pulse(obj) throws SynchronizationLockException if "the calling thread does not own the lock for the specified object".

Weeble
  • 17,058
  • 3
  • 60
  • 75
Jake1234
  • 187
  • 1
  • 7
  • Are you asking "why `lock` is implemented using `Monitor` class and not the other way around" or "why to release a lock with `Wait`/`Pulse` one need to acquire the lock first with `lock` or `Enter`"? Or you asking more about why those exist at all like https://stackoverflow.com/questions/1559293/c-sharp-monitor-wait-pulse-pulseall? – Alexei Levenkov Nov 11 '21 at 00:51
  • Sorry, I've edited the original question to make it clearer. – Jake1234 Nov 11 '21 at 01:02
  • No, your edit does not make question more clear - it was already quite clear what you mean (as everyone can check docs to see usage pattern for example), but it is still unclear why you think it would be of some value to "acquire and release lock as a single operation". It would be nice if you explain what *you* expect `Wait` to do so people can try to clarify that. (Side note you obviously know that ["EDIT:" should not be added](https://meta.stackoverflow.com/questions/255644/should-edit-in-edits-be-discouraged) to post - avoid doing so unless you have some particular statement to make) – Alexei Levenkov Nov 11 '21 at 01:11
  • [Related](https://stackoverflow.com/a/63731860/3181933) – ProgrammingLlama Nov 11 '21 at 01:12
  • @Jake1234 it's a little unclear. feel free to edit the whole thing, or just close it and write a new question – Fattie Nov 11 '21 at 01:13
  • @AlexeiLevenkov Why not make it function like this: in a thread I call ```Wait```, without necessarily acquiring a lock, the parameter only acting as some type of identifier. This starts waiting, atomically. All I expect at this point is that this thread will not start sooner than when something somewhere calls ```PulseAll```. There's no locks (besides those used internally to start waiting/pulse atomically, I guess?). Why not have this type of behaviour as a basic building block? – Jake1234 Nov 11 '21 at 02:33
  • https://en.wikipedia.org/wiki/Monitor_(synchronization) – Alexei Levenkov Nov 11 '21 at 05:38
  • Jake could you include an example demonstrating how the `Wait`/`Pulse` mechanism could be used, if it had the alternative behavior of not requiring to be called by a thread holding the `lock` to the specific object? – Theodor Zoulias Nov 11 '21 at 06:06
  • Honestly I think this question is fine. It's instructive to think about what use `Wait` and `Pulse` would be without the requirement to first hold the lock and, like much of multi-threaded programming, it's not immediately obvious. I think it would be a shame to close the question unless it's actually a duplicate. – Weeble Nov 11 '21 at 22:43
  • @Jake1234 I hope you don't mind my edits, I think I've retained your intent while clarifying the crux of the question. The only part I wasn't 100% sure about was when you said "why not make [them] atomic" so if you could verify I preserved your intent that would help. – Weeble Nov 12 '21 at 12:39
  • @Weeble, no I don't mind, thank you. My point about "why not make them atomic" was that I can see the need for the pulse/wait being thread safe, but I'm not sure why that's not done as internally, as a part of the call ( the same object is passed in as what's used for locking). But it seems from the wiki link, that's simply not what a monitor is, and I can see the the way it's done instead is a lot neater, as very often you do need to do other things than just send/wait for a signal. – Jake1234 Nov 13 '21 at 02:03

2 Answers2

0

Without holding the lock, you will definitely have a race condition. Suppose thread A is going to call Wait and thread B is going to call Pulse. It's important that thread A is already waiting before thread B sends the pulse. If thread B sends the pulse just before thread A starts waiting, then the pulse does nothing and thread A will wait indefinitely. How do we know that thread A is waiting? Well, it could send a message (e.g. setting a flag or putting an item in a queue) before it starts waiting, but then what if sends the message and then another thread gets scheduled before it begins waiting? Just because it has sent the message that it intends to start waiting, doesn't mean that it has yet actually begun waiting! The only way to know that thread A certainly has entered the wait is for A to hold the lock while sending its message, atomically relase the lock as it begins waiting, for B to acquire the lock and then observe A's message.

Now, okay, maybe you could contrive a situation where B determines that A has begun waiting, releases the lock, does some other things and then wants to send a pulse to wake up A after releasing the lock, but you'll run into similar problems when you want to re-use the monitor a second time, or if you want to have more threads waiting or pulsing. Or maybe you say you don't care about missing the pulse, you'll just keep sending more... but then you're devolving into polling and squandering the benefits of Pulse and Wait.

In short, you need to hold the lock before waiting because it's the only way to reliably communicate that there is an entity waiting for a pulse. You need to hold the lock before pulsing because it's the only way to reliably discover that there is an entity ready to receive a pulse. Without these behaviours the Monitor would not be a useful primitive for building more advanced synchronisation mechanisms on top of.

Weeble
  • 17,058
  • 3
  • 60
  • 75
-1

because the purpose of monitor.wait is to release the lock on an object. These are static methods so there is no state indicating what object should be released except via a parameter, hence it must be passed in

Also the aim here is to allow the lock to be used for some purpose, and then released in Monitor.Wait. Having it lock and then unlocked by monitor wouldn't achieve anything

pm100
  • 48,078
  • 23
  • 82
  • 145