Multi-threading article finally "finished" - reviewers welcome

J

Jon Skeet [C# MVP]

TT (Tom Tempelaere) <"TT \(Tom Tempelaere\)" <_|\|_0§P@|/
\|titi____AThotmail.com|/\|@P§0_|\|_>> wrote:

<snip>

I think we'll just have to agree to differ on this one...
 
T

TT \(Tom Tempelaere\)

TT (Tom Tempelaere) <"TT \(Tom Tempelaere\)" <_|\|_0§P@|/
\|titi____AThotmail.com|/\|@P§0_|\|_>> wrote:

<snip>
I think we'll just have to agree to differ on this one...
--
Jon Skeet - <[email protected]>

It seems. The point we probably agree on then, is that one should find a
good pattern and stick to it in every software module.

Cheers,
 
T

TT \(Tom Tempelaere\)

TT (Tom Tempelaere) <"TT \(Tom Tempelaere\)" <_|\|_0§P@|/
\|titi____AThotmail.com|/\|@P§0_|\|_>> wrote:
[...]
I don't really agree with that. It is one of the known and desirable effects
of Monitor.Enter and Exit.

[Jon]
But it only makes sense if you understand memory barriers to start
with.

[TT]
True. Maybe memory barriers need
? Interrupt is a necessity if you wait or join ... I don't know what kind of
threads you write,but there are alot of waits in my program and I don't see
how I would stop them from waiting without an interrupt actually.

[J]
With a pulse, for a call to Wait. That's exactly what pulse is for. I
rarely use Thread.Join, but if I was considering the possibility of the
thread hanging, I'd probably use one of the versions that took a
timeout.

[TT]
One small example of some part in my code (pseudo).

MyTask_1 : Task
MyTask_2 : Task

MyTask_1 t1 = new MyTask_1( ... );
MyTask_2 t2 = new MyTask_2( ... );
TaskExecuter te1 = new TaskExecuter(); // task-thread
TaskExecuter te2 = new TaskExecuter(); // task-thread
te1.AssignTask( t1 );
te2.AssignTask( t2 );
WaitHandle[] taskHandles = {t1.FinishedHandle, t2.FinishedHandle};
WaitHandle.WaitAll( taskHandles );

There is no joining here, just waiting for tasks to finish (the threads
remain alive for performance reasons). The tasks are executed on the
executers which are threads designed to do so. There is no reasonable
timeout because the execution time for the tasks can vary a lot.

I don't see how a Wait + Pulse pattern fits in here. Can one pulse a WaitAll
operation?
Perhaps you have an alternative? And what if the thread doesn't repond. It happens
you know. Then abort is about the best you can do.

[Jon]
Occasionally. A lot of the time I'd rather have a hung thread and abort
the whole process than abort a single thread and have it doing
potentially dangerous stuff.

[TT]
I agree with what you say. Recovery is the best you can do. Reboot the
computer, or reboot the process if this possible (I've never done that). It
is probably ez for a service to do that, I should check that...
There is no avoiding
there the way I see it. Everything has its purpose in System.Threading. They
didn't put it in for the show.

[Jon]
Well, I don't have to necessarily agree that everything should be used.
(Look at the Java API, where Thread.stop() is deprecated as being
basically unsafe.)

Besides, I've seen some evidence that an aborted thread in .NET can
have nasty effects, such as coming out of a Wait without regaining a
lock first. I don't want such a thread running around potentially
causing havoc with the rest of my well-threaded code.
--
Jon Skeet - <[email protected]>

[TT]
No indeed you don't Jon. But the deprecated stuff in Java is not to be used
of course. And the fact that it was introduced and then deprecated shows
that Java had (serious?) design mistakes from the start. Let's hope that
this not true for .NET.
Now I never done threading in Java (although I've read about it), but you
made me curious as to why Java/Thread::stop was deprecated. Because people
didn't use it correctly, or because it didn't/couldn't do as was documented,
or it did things which are extremely bad for program state/flow.

I agree with you on Thread::Abort: it is not a safe method to use, and there
are nasty side effects (such as jumping out of finally blocks etc).

Cheers,
 
T

TT \(Tom Tempelaere\)

TT (Tom Tempelaere) said:
But it only makes sense if you understand memory barriers to start
with.
[TT]
True. Maybe memory barriers need

a seperate chapter. It could bundle the different ways to achieve this.
Enfin perhaps this is beyound the scope of your article. The links are
enough then.
 
J

Jon Skeet [C# MVP]

TT (Tom Tempelaere) <"TT \(Tom Tempelaere\)" <_|\|_0§P@|/
\|titi____AThotmail.com|/\|@P§0_|\|_> said:
It seems. The point we probably agree on then, is that one should find a
good pattern and stick to it in every software module.

Yes - but only until a significantly better pattern is found, at which
point it's worth using that instead. I wouldn't want to rigidly stick
to a pattern I'd found to be inelegant.
 
T

TT \(Tom Tempelaere\)

TT (Tom Tempelaere) <"TT \(Tom Tempelaere\)" <_|\|_0§P@|/
\|titi____AThotmail.com|/\|@P§0_|\|_> said:
It seems. The point we probably agree on then, is that one should find a
good pattern and stick to it in every software module.

Yes - but only until a significantly better pattern is found, at which
point it's worth using that instead. I wouldn't want to rigidly stick
to a pattern I'd found to be inelegant.
--
Jon Skeet - <[email protected]>

Of course.

Regarding threads, it probably also depends on what the "threading" factor
is in your software. If all you do is simple threading stuff then you don't
need to make a automizing framework for it. But if you find yourself coding
things again and again, then an automizing pattern is nice to have.

Cheers,
 
J

Jon Skeet [C# MVP]

TT (Tom Tempelaere) <"TT \(Tom Tempelaere\)" <_|\|_0§P@|/
\|titi____AThotmail.com|/\|@P§0_|\|_> said:
[TT]
Maybe memory barriers need a seperate chapter. It could bundle the
different ways to achieve this. Enfin perhaps this is beyound the
scope of your article. The links are enough then.

Well, I've already got a section dealing with why memory barriers are
needed and what they're for - I just need to put a link to that section
within the Monitors one.
[J]
With a pulse, for a call to Wait. That's exactly what pulse is for. I
rarely use Thread.Join, but if I was considering the possibility of the
thread hanging, I'd probably use one of the versions that took a
timeout.

[TT]
One small example of some part in my code (pseudo).

<snip>

Ah - I thought you were talking about Monitor.Wait, that being the one
I usually use.

I still don't really like using Thread.Interrupt in such a situation -
if it ends up occurring just after the WaitAll happens, the next
operation will be interrupted, so that needs to be thought about
carefully, etc.
Perhaps you have an alternative? And what if the thread doesn't repond.It happens
you know. Then abort is about the best you can do.

[Jon]
Occasionally. A lot of the time I'd rather have a hung thread and abort
the whole process than abort a single thread and have it doing
potentially dangerous stuff.

[TT]
I agree with what you say. Recovery is the best you can do. Reboot the
computer, or reboot the process if this possible (I've never done that). It
is probably ez for a service to do that, I should check that...

I believe a service can just die, and with recent versions of Windows
you can tell the service control manager to restart on error. If you're
using an old version of Windows, you could always run the service
within a wrapper which starts the real service and monitors the
process.
[Jon]
Well, I don't have to necessarily agree that everything should be used.
(Look at the Java API, where Thread.stop() is deprecated as being
basically unsafe.)

Besides, I've seen some evidence that an aborted thread in .NET can
have nasty effects, such as coming out of a Wait without regaining a
lock first. I don't want such a thread running around potentially
causing havoc with the rest of my well-threaded code.

[Aargh - *please* don't quote my sig unquoted, as it were - my
newsreader automatically chops off anything below a sig separator, so I
had to manually cut and paste the bit below separately. The sig should
almost *always* be snipped on a reply.]
[TT]
No indeed you don't Jon. But the deprecated stuff in Java is not to be
used of course. And the fact that it was introduced and then deprecated
shows that Java had (serious?) design mistakes from the start.

I wouldn't say that - I suspect Java decided to pretty much copy
existing interfaces, and then realised how unsafe they can be. It's a
shame they didn't realise that before, of course, but I think that
shows them copying other people's design mistakes rather than making
their own new ones. (It's not like the Java libraries are the only ones
to have deprecation...)
Let's
hope that this not true for .NET. Now I never done threading in Java
(although I've read about it), but you made me curious as to why
Java/Thread::stop was deprecated. Because people didn't use it
correctly, or because it didn't/couldn't do as was documented, or it
did things which are extremely bad for program state/flow.

Stopping threads unnaturally is inherently bad - that's the problem.
What do you do with their locks, for instance? If you release them, you
may end with bad state. If you don't release them, you end up with
other threads deadlocking and then *they* have to be stopped.

See
http://java.sun.com/j2se/1.4.2/docs/guide/misc/threadPrimitiveDeprecati
on.html

aka http://tinyurl.com/2z6zb
I agree with you on Thread::Abort: it is not a safe method to use,
and there are nasty side effects (such as jumping out of finally blocks
etc).

Does the fact that it's not a safe method to use not mean that it .NET
had serious design mistakes from the start?

(At least Thread.interrupt in Java doesn't allow code to continue from
a wait without regaining the lock it lost - unlike .NET currently,
IIRC!)
 
T

TT \(Tom Tempelaere\)

TT (Tom Tempelaere) <"TT \(Tom Tempelaere\)" <_|\|_0§P@|/
\|titi____AThotmail.com|/\|@P§0_|\|_> said:
[J]
With a pulse, for a call to Wait. That's exactly what pulse is for. I
rarely use Thread.Join, but if I was considering the possibility of the
thread hanging, I'd probably use one of the versions that took a
timeout.

[TT]
One small example of some part in my code (pseudo).

<snip>

Ah - I thought you were talking about Monitor.Wait, that being the one
I usually use.

I still don't really like using Thread.Interrupt in such a situation -
if it ends up occurring just after the WaitAll happens, the next
operation will be interrupted, so that needs to be thought about
carefully, etc.

[TT]: My newsreader has trouble indenting your posts :(. Anyway,

You are absolutely right. I only use it to shut down threads that wait
(WaitHandle waits) or that wait a lot. There should of course be no waiting
operations in the cleanup phase of the thread. I should check on that.
Let's
hope that this not true for .NET. Now I never done threading in Java
(although I've read about it), but you made me curious as to why
Java/Thread::stop was deprecated. Because people didn't use it
correctly, or because it didn't/couldn't do as was documented, or it
did things which are extremely bad for program state/flow.

Stopping threads unnaturally is inherently bad - that's the problem.
What do you do with their locks, for instance? If you release them, you
may end with bad state. If you don't release them, you end up with
other threads deadlocking and then *they* have to be stopped.

See
http://java.sun.com/j2se/1.4.2/docs/guide/misc/threadPrimitiveDeprecati
on.html

aka http://tinyurl.com/2z6zb
I agree with you on Thread::Abort: it is not a safe method to use,
and there are nasty side effects (such as jumping out of finally blocks
etc).

Does the fact that it's not a safe method to use not mean that it .NET
had serious design mistakes from the start?

(At least Thread.interrupt in Java doesn't allow code to continue from
a wait without regaining the lock it lost - unlike .NET currently,
IIRC!)
--
Jon Skeet - <[email protected]>
http://www.pobox.com/~skeet
If replying to the group, please do not mail me too

No it doesn't. I see your point.

Cheers,
 
W

William Stacey [MVP]

Occasionally. A lot of the time I'd rather have a hung thread and abort
the whole process than abort a single thread and have it doing
potentially dangerous stuff.

Agree to an extent. Your program should cover a thread abort around any
wait anyway, so it is safe if your code handles this condition correctly.
Thread abort has some value in that you don't need to wait(x) just to check
a flag (i.e. stop) and cycle around again This is ok, but causes pooling
and complicates your code. Using abort, you can just wait until the abort
comes (if ever.) I don't really like using exceptions in this way either,
but I see why they did it as apposed to returning false on the wait and
having to check another var if it failed because of a timeout or an abort.
Besides, I've seen some evidence that an aborted thread in .NET can
have nasty effects, such as coming out of a Wait without regaining a
lock first. I don't want such a thread running around potentially

That is a pretty big issue if that happens. Do you have any doco or code
samples that can repro this behavior? TIA
 
J

Jon Skeet [C# MVP]

TT (Tom Tempelaere) <"TT \(Tom Tempelaere\)" <_|\|_0§P@|/
\|titi____AThotmail.com|/\|@P§0_|\|_> said:
Regarding threads, it probably also depends on what the "threading" factor
is in your software. If all you do is simple threading stuff then you don't
need to make a automizing framework for it. But if you find yourself coding
things again and again, then an automizing pattern is nice to have.

Oh certainly I'd agree on that front.
 
J

Jon Skeet [C# MVP]

TT (Tom Tempelaere) <"TT \(Tom Tempelaere\)" <_|\|_0§P@|/
\|titi____AThotmail.com|/\|@P§0_|\|_> said:
[TT]: My newsreader has trouble indenting your posts :(.

Please at least cut out my signature then - otherwise it's a pain to
reply to the bit that you write after it. (I'd also suggest changing
your newsreader, or getting OE Quotefix :)
Anyway,

You are absolutely right. I only use it to shut down threads that wait
(WaitHandle waits) or that wait a lot. There should of course be no waiting
operations in the cleanup phase of the thread. I should check on that.

But it'll be interrupted next time it blocks for *any* reason, I
believe. It's probably not too bad if you can absolutely guarantee that
the WaitAll isn't going to return, but I would imagine that the kind of
situation in which you use it would prohibit that anyway.
 
J

Jon Skeet [C# MVP]

William Stacey said:
True, but can dead-lock anything if your code it that way.

Only if someCollection.SyncRoot==someCollection in this case. Why bring
that possibility into things? Using independent locks makes it a lot
cleaner, IMO.
 
J

Jon Skeet [C# MVP]

William Stacey said:
Agree to an extent. Your program should cover a thread abort around any
wait anyway, so it is safe if your code handles this condition correctly.
Thread abort has some value in that you don't need to wait(x) just to check
a flag (i.e. stop) and cycle around again This is ok, but causes pooling
and complicates your code.

It's *much* cleaner, IMO, in terms of knowing what's happening.
Using abort, you can just wait until the abort
comes (if ever.) I don't really like using exceptions in this way either,
but I see why they did it as apposed to returning false on the wait and
having to check another var if it failed because of a timeout or an abort.

Well, Monitor.Wait returns a boolean to say whether or not the lock was
reacquired before the timeout anyway. I would be *very* worried at any
use I had to make of Thread.Abort.
That is a pretty big issue if that happens. Do you have any doco or code
samples that can repro this behavior? TIA

Sure (it's interrupted rather than aborted threads, it turns out, but
even so). This is a modified version of something that was posted a
while ago. Note that if the interrupting thread is the "main" thread it
doesn't seem to happen...

using System;
using System.Threading;

class Test
{
static object padlock = new object();

static Thread waiter;

static void Main()
{
waiter = new Thread (new ThreadStart(LockAndWait));
waiter.Start();

// Let it start waiting
Thread.Sleep(500);

Thread interrupter = new Thread
(new ThreadStart(PulseAndInterrupt));
interrupter.Start();
}

static void PulseAndInterrupt()
{
lock (padlock)
{
Console.WriteLine ("About to interrupt");
Monitor.Pulse(padlock);
waiter.Interrupt();

while (true)
{
Console.WriteLine
("Interrupter thread still got the lock...");
Thread.Sleep(1000);
}
}
}

static void LockAndWait()
{
lock (padlock)
{
try
{
Monitor.Wait(padlock);
}
catch (ThreadInterruptedException)
{
Console.WriteLine
("Caught ThreadInterruptedException");
}
while (true)
{
Console.WriteLine
("Waiter thread got the lock?");
Thread.Sleep(1000);
}
}
}
}
 
W

William Stacey [MVP]

Only if someCollection.SyncRoot==someCollection in this case. Why bring
that possibility into things?

Sorry, not following you. This is possible deadlock regardless if locking
on "this" or another object.
T1
lock(somethingElse)
{
lock(obj.SyncRoot) // or "this"
{
}
}

T2
lock(obj.SyncRoot) // or "this"
{
lock(somethingElse)
{
}
}
Using independent locks makes it a lot
cleaner, IMO.

"this" is independent. Moreover, if you add a 1000 objects that contain
syncRoot objects, you add almost 8K of memory for just the seperate lock
objects(assuming 8 bytes, could be more or less. Sure someone will let me
know.) This may not be much for some folks, but is it is not a trivial
amount of memory.
 
J

Jon Skeet [C# MVP]

William Stacey said:
Sorry, not following you. This is possible deadlock regardless if locking
on "this" or another object.
T1
lock(somethingElse)
{
lock(obj.SyncRoot) // or "this"
{
}
}

T2
lock(obj.SyncRoot) // or "this"
{
lock(somethingElse)
{
}
}

That's not the code I posted though. I posted:

<quote>
lock (someCollection)
{
lock (somethingElse)
{
...
}
}

Another thread does:

lock (somethingElse)
{
lock (someCollection.SyncRoot)
{
...
}
}
</quote>

If obj and obj.SyncRoot are different references, there's no dead-lock
in the above code.
"this" is independent. Moreover, if you add a 1000 objects that contain
syncRoot objects, you add almost 8K of memory for just the seperate lock
objects(assuming 8 bytes, could be more or less. Sure someone will let me
know.) This may not be much for some folks, but is it is not a trivial
amount of memory.

Well, you've already got *at least* that much memory anyway for the
other objects - and chances are those objects are going to be doing
something useful, especially if they're collections. If you've got a
thousand totally empty collections (which last beyond gen0) I suspect
you're in an unusual situation. (Collections tend to have a bit of
buffer space left over most of the time - do you call
ArrayList.TrimToSize on every ArrayList you create?)

I'd rather have code which was easy to maintain at the cost of a few K
of memory here and there than unmaintainable but efficient code.
 
W

William Stacey [MVP]

That's not the code I posted though. I posted:

Thanks, I know. It does not matter what code you post, it is only one
example. Anytime you have two locks, you could dead lock yourself if you
don't do it right everywhere you lock the objects. It does not matter what
those objects are, from that perspective.
Well, you've already got *at least* that much memory anyway for the
other objects - and chances are those objects are going to be doing
something useful, especially if they're collections. If you've got a

Natuarally. However we were not talking about locking on collection object.
IIRC, you were talking about always using another object just for locking
(i.e. see padlock in prior post.) Moreover, in all the .Net collections (I
think) "this" is what SyncRoot refers to. So your back to what you don't
like if your locking on .Net ICollection objects.
I'd rather have code which was easy to maintain at the cost of a few K
of memory here and there than unmaintainable but efficient code.

We all would agree with that in general I think. But your making a leap
that one will equal the other. I don't think that is true in general.
Anyway, at this point we are not really getting anywhere (and I don't feel
that strongly about this), so please have the last word. Cheers.
 
J

Jon Skeet [C# MVP]

William Stacey said:
Thanks, I know. It does not matter what code you post, it is only one
example. Anytime you have two locks, you could dead lock yourself if you
don't do it right everywhere you lock the objects. It does not matter what
those objects are, from that perspective.

But the code I posted was absolutely fine if the sync root isn't the
object itself. Yes, the code leaves you open to the possibility of
deadlock if the implementation uses the object itself as its SyncLock,
but why actually make that risk a problem if you (as the collection
implementer) don't have to?
Natuarally. However we were not talking about locking on collection object.

Were we not? I thought we were. What else has a sync root? We'd been
talking about collections for the past few posts...
IIRC, you were talking about always using another object just for locking
(i.e. see padlock in prior post.) Moreover, in all the .Net collections (I
think) "this" is what SyncRoot refers to. So your back to what you don't
like if your locking on .Net ICollection objects.

Absolutely - I've never suggested that you *should* lock on ICollection
objects.
We all would agree with that in general I think. But your making a leap
that one will equal the other. I don't think that is true in general.
Anyway, at this point we are not really getting anywhere (and I don't feel
that strongly about this), so please have the last word. Cheers.

I'm suggesting that it's easier to maintain code if you have a simple
policy - always lock on a private lock or a sync root. If you're
*providing* a sync root, make sure that reference isn't available in
any other way. That policy costs a little bit more memory, but I
believe it makes things simpler.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top