System.Threading.Monitor question

  • Thread starter Thread starter Jon Shemitz
  • Start date Start date
J

Jon Shemitz

Exploring the C# lock, I wrote the simple multi-threaded IEnumerator
consumer, below. It simply reads elements and adds them to List, a
private ArrayList member of the same object as threadProc().

I originally wrote this without the Thread.Sleep(0); line, and I found
that - no matter how big the list or how many threads - one thread
would consume the whole list. Adding the Thread.Sleep(0); line let the
threads share the list as expected.

From this I conclude
lock() (System.Threading.Monitor.Enter()) will not block
if another thread is waiting on the same object;
only if another thread already has it locked.

Correct?

private void threadProc()
{
object Current;
do
{
lock (Enumerator)
Current = Enumerator.MoveNext()
? Enumerator.Current
: null;
if (Current != null)
List.Add(Current);
Thread.Sleep(0);
} while (Current != null);
}
 
no, this is circular reasoning. the fact that another thread is waiting
necessarily means that another thread already has a lock. put another way,
how can a thread be blocked waiting if there is no lock? a block is a result
of a lock. except if you confuse blocking with sleeping, in that case you'd
have to examine the threadstate to be sure of what state the thread is in.
make sense?
 
Alvin Bruney said:
no, this is circular reasoning. the fact that another thread is waiting
necessarily means that another thread already has a lock. put another way,
how can a thread be blocked waiting if there is no lock? a block is a result
of a lock. except if you confuse blocking with sleeping, in that case you'd
have to examine the threadstate to be sure of what state the thread is in.
make sense?

I'm not sure. State diagrams are hard to do in ASCII, so bear with my
words:

Thread A starts first, so gets a lock first. While Thread A has a
lock, B comes along and tries to get a lock; this blocks B. (The same
might happen to B and C and ....)

Thread A unlocks, but this doesn't immediately wake B; B will only get
woken by the dispatcher, between quanta. Thread A does its thing with
the data it got from the shared resource, and promptly loops around
and relocks. Because the code in the original message is so trivial,
it relocks IN THE SAME QUANTA.

I'm not sure of this reasoning, because it DOES require that A yield
while it has a lock and not yield between unlocking and relocking -
but, in this case, that's not totally implausible because

Current = Enumerator.MoveNext() ? Enumerator.Current : null;

is probably pricier than

if (Current != null)
List.Add(Current);

and ... I'm not sure how else to explain the way that WITHOUT the
Thread.Sleep(0); line, Thread A **always** consumed the whole list.
 
Jon said:
I'm not sure how else to explain the way that WITHOUT the
Thread.Sleep(0); line, Thread A **always** consumed the whole list.

Oh, my bad. I thought I'd found that

Just plain not true. I simply wasn't using lists big enough for this
trivial consumer. When I increased list size to 500000 a second thread
starts to get some of the entries; at 750000, a third thread can get
in there, &c.
 
Back
Top