Why does Thread class not support IDisposable?

  • Thread starter Thread starter Creativ
  • Start date Start date
Yes. That's what I'm wondering about.
Since there is no direct relation between memory consumption pressure
and "thread consumption pressure", we cannot rely solely on GC. That's
why I'm wondering why Thread doesn't implement Dispose.
 
Lasse Vågsæther Karlsen said:
I think there is a misunderstand between what the poster is asking for
and what you all are answering.

The problem, if I'm correct, isn't that he wants to dispose of a running
thread. The problem is that the handles being used by the thread is kept
until the object is collected, and thus the finalizer is called. This
happens, naturally, at a nondeterministic time after the point when the
thread method has stopped running.

That's what I'd understood as well. Hence:

<quote>
There are typically many, many, *many* times more handles available
than you sensibly want to create threads - in other words, if you're
creating enough threads to run into this as a problem, then you're
doing something wrong anyway.
</quote>

Basically, I can see why it's a *theoretical* problem - but it only
becomes a *practical* problem if you've got bigger problems anyway.

Since I can't seem to find any method on the Thread object that would
mimick the usual case of a Close or Dispose method, IMO the handles will
be "in use" until the object is disposed of. In all other discussions I
see about using scarce resources in .NET, dispose is the way to go, but
Thread is apparently special. How so? What am I missing here?

I *suspect* that the problem is that closing the handle of a thread
prematurely would have nastier effects than closing the handle of, say,
an Image before something else tries to access it.
 
Lasse Vågsæther Karlsen said:
I think there is a misunderstand between what the poster is asking for and
what you all are answering.

The problem, if I'm correct, isn't that he wants to dispose of a running
thread. The problem is that the handles being used by the thread is kept
until the object is collected, and thus the finalizer is called. This
happens, naturally, at a nondeterministic time after the point when the
thread method has stopped running.

To test this hypothesis, I created a list of 1000 thread objects. I
created the threads and kept then in a suspended state waiting for an
event, and watched the number of handles soar. Each thread object uses up
5 handles, as reported by taskinfo (www.iarsn.com).

Then I asked them all to finish, but kept the references to the thread
objects in my list, this made all the thread methods exit, as I could see
lots of messages in the output window about threads exiting, and TaskInfo
also removed all the threads from its list. However, the handles were
still in use.

Then finally I cleared the list and forced a collection, at this point the
handle count reported by TaskInfo dropped back to the initial level.

Note that I agree with the sentiment of the posts here, if you get to the
point where .NET complains about reaching the limit then you're most
likely doing something wrong, but I also got to ask if the thread object
is regarded so special as to not allow us to clean up the resources
deterministically. And note once again that I'm not talking about yanking
out the handles underneath the running thread.

Since I can't seem to find any method on the Thread object that would
mimick the usual case of a Close or Dispose method, IMO the handles will
be "in use" until the object is disposed of. In all other discussions I
see about using scarce resources in .NET, dispose is the way to go, but
Thread is apparently special. How so? What am I missing here?

Or is there some special case code in there that monitors the amount of
lingering to-be-collected Thread objects and forces a collection if the
number gets too high? Is it just me or could this lead to a strange
problem in a system that uses a lot of threads but for some reason has so
much memory that GC is happening at very long intervals?




The 5 object handles you see are:
1 OS thread handle
4 event object handles associated with the thread and used by the CLR for
synchronization purposes.

Native thread management is completely handled within the current
implementation of CLR, the closing of the event handles have to be done in a
well defined order and must be done before closing the thread handle, which
can only de done after the managed thread object has been collected. That
means that the Disposable pattern is not usable with the current CLR's
implementation (which predates the Disposable pattern anyway).
As Jon correctly states, you'll have much bigger problems before the handles
become a problem, also don't forget that the GC runs more often than you may
think. Sure, you can create/start threads without (almost) allocating
objects, which means that the GC will probably never run, but you will
exhaust your Virtual Memory resources long before you will exhaust the
handle table entries in your process, even (just) before you reach the VM
limits of the process, the CLR will force a GC run, possibly running the
finalizers on the dead threads.

Willy.
 
Let me elaborate my question more. When the execution is ready, when
CloseHandle will be called?

"Ready"? Do you mean, when the start delegate for the thread returns?

I'm not sure that in .NET you can say that it does. In at least some
incarnations of the framework, there is not a 1-to-1 correspondence
between a .NET managed thread, and an actual OS thread.
I cannot find the details about that. If CloseHandle is called in the
Finalizer, call CloseHandle in Dispose would give a user an
deterministic way to close that handle.

If you really want to know, you could use Reflector to look at the
implementation of Thread and see what it does. But I would be surprised
if the Thread class even has a finalizer. Generally speaking, a finalizer
and IDisposable go hand in hand. There may be exceptions, but surely they
aren't frequent.

Given that, if closing the thread handle is a requirement to free up the
address space used by the thread (I'm not sure it is...never really gave
it much thought, I admit since normally the right way to do it is not run
so many threads it becomes an issue), I would expect that the Thread class
would close the handle immediately after the start delegate has returned.

The bottom line: you don't need IDisposable on the Thread class. If
you're running into resource management problems with the Thread class,
it's not because it doesn't implement IDisposable.

Pete
 
Chris Mullins said:
If you've got all of the threadpool threads busy, you may end up deadlocking
the entire threadpool.

You can exhaust the threadpool, of course, but that's when items start
to get queued.
performance waaaaay worse than a single-threaded soultuion. You're using all
system resources creating and scheduling threads. Almost no work is being
done towards your actual problem set.

Well, isn't that the reason why the threadpool should have a sensible
maximum number of threads? To keep the scheduling overhead sensible and
avoid too much concurrency. That's why the built-in threadpools size
depends on the number of CPUs/cores, isn't it?
... breaking your work down into chunks, and passing those chungs to a set
of threads via a queue, is often the right design. You just can't point
1000+ threads at the queue and say "go!". At least not on a 1x, 2x, 4x, 8x
processor box.

Well, you'd have around 25-50 threads on a typical thread pool on a
single/dual-core computer. If you have more tasks, they'll get queued
up.

Best regards,
Martin
 
You can exhaust the threadpool, of course, but that's when items start
to get queued.

That's not what Chris is talking about. He's referring to a design bug in
which code executing in a thread pool thread waits for another thread
executing in a thread pool thread to complete. IMHO, no one should ever
write code like this anyway, but if they do they can create a situation
where all of the thread pool threads are stuck waiting on other threads
(typically threads they created, but that's not important) that have all
been queued and won't execute until a currently running thread pool thread
finishes its task.
Well, isn't that the reason why the threadpool should have a sensible
maximum number of threads? To keep the scheduling overhead sensible and
avoid too much concurrency. That's why the built-in threadpools size
depends on the number of CPUs/cores, isn't it?

As I wrote in my other post, once you have more than twice as many
constantly executing threads than CPUs, there's no additional overhead
from scheduling. The only real overhead there is the context switch, and
once all threads are taking their entire timeslice, you don't get any more
context switch overhead with more threads than you would have with that
baseline number.

With more threads comes the greater possibility of repeatedly voiding the
CPU cache of useful data, but again even a small number of threads can do
that. I don't think that the possibility is a significant factor in the
choice of the number of threads in a thread pool.

Instead, I think that the number of threads in a thread pool is primarily
dictated by two balancing factors: the cost of a thread, and the need to
ensure that you always have some thread runnable, to maximize your use of
the CPU. When your tasks are entirely CPU-bound, this is less of an
issue: just make one thread for each CPU and let it run. But many
problems are at least partially i/o-bound. In that case, you want enough
threads to ensure that when some of those threads wind up waiting on i/o,
there are other threads that can be executed taking advantage of the
available CPU.

On the other side of the balance is the cost of a thread. With 1MB of
stack space for a thread (by default), and a 2GB limit in a processes
virtual address space, you can have only 2048 threads total, in theory (of
course, in practice it's even less than that because other things compete
for the process address space). Without this cost, you would just let the
thread pool grow unbounded, because after all that would maximize your
chances of always having something to run.
Well, you'd have around 25-50 threads on a typical thread pool on a
single/dual-core computer. If you have more tasks, they'll get queued
up.

But as Chris points out, that's not true any more. As of the first
service pack for .NET 2.0, the maximum number of active threads in the
default thread pool is 250 per CPU.

I believe that was his point, or at least part of it. I agree that with
the original limit of 25, it's not a problem to throw a bunch of tasks at
the thread pool. But unfortunately the Microsoft folks decided to work
around badly designed applications by raising the limit to 250 from 25.
This creates a significant risk of consuming process address space for a
few applications that assume the limit is lower, have a very large number
of tasks, and use the thread pool for their task queuing.

I guess the lesson here (or at least one of them) is that if you're going
to use a thread pool to handle your task queuing, include code to make
sure that the maximum thread count of threads for the thread pool is
something reasonable. :)

Pete
 
Peter Duniho said:
That's not what Chris is talking about. He's referring to a design bug in
which code executing in a thread pool thread waits for another thread
executing in a thread pool thread to complete. IMHO, no one should ever
write code like this anyway, but if they do they can create a situation
where all of the thread pool threads are stuck waiting on other threads
(typically threads they created, but that's not important) that have all
been queued and won't execute until a currently running thread pool thread
finishes its task.

The problem is, it's really easy to write this broken code. By mistake.

For example, I first came across this because our methods, running on
threadpool threads, were calling SQL Server.

We happened to hit a case where all 25 threadpool threads were calling into
SQL Server at once. This means, there was no threadpool thread available for
ADO.Net to use, and completed operations just got queued up in the
ThreadPool Work Queue. Meanwhile all 25 threads were waiting for results.
End result: Deadlocked process.

.... the underlying problem is that .Net Internals make use of the threadpool
all over the place. If you've got all the threads busy, the Framework can't
do it's job, and your entire process is at risk for hanging.

This is (in my opinion) easily solved by using a different thread pool for
your own stuff. Jon Skeet's is fine, and Jeff Richter (as part of his Power
Threading library) has a nice Thread Pool implementation.

The new threading stuff in Vista (not .Net, only accessable via Win32) looks
really nice. They unified the IOCP and Process thread pool, and give code
the ability to create additional pools withing the process. Now I just need
them to port that back to XP / Win2k3 and write a bunch of managed code
around it. I'm not holding my breath...
 
Martin Carpella said:
You can exhaust the threadpool, of course, but that's when items start
to get queued.

Yea. That's true. ... but there are bugs in the current threadpool
implementation that makes it possible to deadlock the ThreadPool by mistake.

The way I personally discovered this is by doing I/O operations from a
ThreadPool thread. Under load, we had all threadpool threads doing I/O at
the same time, which caused the problem. More detail about my discover of
this can be found at:
http://www.coversant.com/dotnetnuke/Default.aspx?tabid=88&EntryID=8
Well, you'd have around 25-50 threads on a typical thread pool on a
single/dual-core computer. If you have more tasks, they'll get queued
up.

The threadpool, to help avoid the deadlock bug I'm describing above, was
updated in a Service Pack. The default limit was grown from 25 threads to
250 threads per process.

Details at:
http://www.bluebytesoftware.com/blog/PermaLink,guid,ca22a5a8-a3c9-4ee8-9b41-667dbd7d2108.aspx
 
Peter Duniho said:
You can exhaust the threadpool, of course, but that's when items start
to get queued.

That's not what Chris is talking about. He's referring to a design bug
in
which code executing in a thread pool thread waits for another thread
executing in a thread pool thread to complete. IMHO, no one should ever
write code like this anyway [...]

The problem is, it's really easy to write this broken code. By mistake.

Well, I don't disagree that's a problem. But I don't think the fix is to
increase the maximum number of threads in the pool by a factor of 10.
For example, I first came across this because our methods, running on
threadpool threads, were calling SQL Server.

We happened to hit a case where all 25 threadpool threads were calling
into
SQL Server at once. This means, there was no threadpool thread available
for
ADO.Net to use, and completed operations just got queued up in the
ThreadPool Work Queue. Meanwhile all 25 threads were waiting for results.
End result: Deadlocked process.

It's my opinion that it's a design bug for .NET to be susceptible to this
problem. Yes, it's a problem that exists now. Yes, it's a hard problem
to solve correctly. But it's not impossible to solve correctly, and I
don't think the increase in max threads for the pool is the right way to
solve it.
... the underlying problem is that .Net Internals make use of the
threadpool
all over the place. If you've got all the threads busy, the Framework
can't
do it's job, and your entire process is at risk for hanging.

See above. IMHO, it's a design bug for .NET to allow the user-written
code to consume a resource that .NET requires in order to function
correctly. They could have reserved some number of thread pool threads as
exclusive for .NET use, or they could have provided the .NET internals
with a completely different thread pool. There are probably other
solutions I'm not thinking of a that moment. Any of them would be better
than just throwing more threas at the problem (especially since doing so
doesn't actually _fix_ the problem...it just makes it harder to reproduce).

One very important design criteria of any OS component, including a
framework like .NET, is that its own internal operation should be immune
from any particulars in client software. This is a theoretical ideal --
obviously there are limits to just how immune an OS can be -- but in this
case there's no reason .NET couldn't be immune. It just isn't. :(

Pete
 
Peter Duniho said:
Well, I don't disagree that's a problem. But I don't think the fix is to
increase the maximum number of threads in the pool by a factor of 10.

It's certainly not an elegant fix, or indeed one that will solve all
problems. However, I suspect that it *is* a pragmatic fix:

1) It will make the problem disappear in a large number of realistic
cases

2) It shouldn't break anything else, including anything assuming
details of what gets run on a threadpool thread.

It's not nice, but it may well be the "least bad" option available at
the moment.
 
Chris Mullins said:
Yea. That's true. ... but there are bugs in the current threadpool
implementation that makes it possible to deadlock the ThreadPool by
mistake.

The way I personally discovered this is by doing I/O operations from a
ThreadPool thread. Under load, we had all threadpool threads doing I/O at
the same time, which caused the problem. More detail about my discover of
this can be found at:
http://www.coversant.com/dotnetnuke/Default.aspx?tabid=88&EntryID=8


The threadpool, to help avoid the deadlock bug I'm describing above, was
updated in a Service Pack. The default limit was grown from 25 threads to
250 threads per process.


Chris,

You mean "250 threads per processor" isn't it? Note that the SP you are
talking about is SP1 for the .NET Framework 2, which is part of 3.5 but also
available as a stand-alone download, so it can be applied without the need
to install 3.5.
With this SP we now have a redesigned TP with a default maximum of 250 TP
per processor and 1000 IOCP threads per process.

Willy.
 
It's certainly not an elegant fix, or indeed one that will solve all
problems. However, I suspect that it *is* a pragmatic fix:

Well, given how broadly "pragmatic" can be interpreted, I can't argue with
that. :) But...
1) It will make the problem disappear in a large number of realistic
cases
Yes.

2) It shouldn't break anything else, including anything assuming
details of what gets run on a threadpool thread.

No. As I pointed out, it could easily break an application that assumes
it can send thousands of work items to the thread pool without any real
problems. Even if doing so doesn't actually consume all of the address
space (that would require either an 8-core system, or a 4-core system
along with code that does enough i/o to cause 1000 IOCP threads to be
created), it could easily result in a significant decrease in virtual
address space available for other things.
It's not nice, but it may well be the "least bad" option available at
the moment.

I guess it's all relative. But for a "service pack" level of update, I
would expect something better. It's definitely not true that the change
is harmless.

Pete
 
Peter Duniho said:
No. As I pointed out, it could easily break an application that assumes
it can send thousands of work items to the thread pool without any real
problems. Even if doing so doesn't actually consume all of the address
space (that would require either an 8-core system, or a 4-core system
along with code that does enough i/o to cause 1000 IOCP threads to be
created), it could easily result in a significant decrease in virtual
address space available for other things.

Well, only if those items are queued for a long time - don't forget
that the ThreadPool still has a "ramp up" time before it creates new
threads. Queuing 250 items won't start 250 threads unless they're there
for a really long time. (I think it starts up at most one thread a
second, or at least something of that order.)
 
Peter Duniho said:
That's not what Chris is talking about. He's referring to a design bug
in which code executing in a thread pool thread waits for another
thread executing in a thread pool thread to complete.

Ups, sorry, I completely misunderstood this.
IMHO, no one
should ever write code like this anyway, but if they do they can create
a situation where all of the thread pool threads are stuck waiting on
other threads (typically threads they created, but that's not
important) that have all been queued and won't execute until a
currently running thread pool thread finishes its task.

I know, I managed to produce such a bug myself by exhausting the
ThreadPool with BeginInvoke().
But as Chris points out, that's not true any more. As of the first
service pack for .NET 2.0, the maximum number of active threads in the
default thread pool is 250 per CPU.

I see, this particular change slipped my notice and I totally agree with
you regarding to the consequences for the process space in a 32bit process.

Thanks for clarifying my misunderstanding!

Best regards,
Martin
 
Chris Mullins said:
Yea. That's true. ... but there are bugs in the current threadpool
implementation that makes it possible to deadlock the ThreadPool by
mistake.

I noticed this myself, by using BeginInvoke() and several
BackgroundWorkers I managed to exhaust the available threads in the
pool. Changed the complete design afterwards to fix the problem (was a
better solution then, anyway).
The threadpool, to help avoid the deadlock bug I'm describing above, was
updated in a Service Pack. The default limit was grown from 25 threads to
250 threads per process.

Details at:
http://www.bluebytesoftware.com/blog/PermaLink,guid,ca22a5a8-a3c9-4ee8-9b41-667dbd7d2108.aspx

Thanks for your update. I missed this change. Sorry for argumenting with
outdated information.

Beste regards,
Martin
 
You mean "250 threads per processor" isn't it?

Of course. That'll teach me (again!) to not proofread my posts a 3rd or 4th
time.
With this SP we now have a redesigned TP with a default maximum of 250 TP
per processor and 1000 IOCP threads per process.

You mean "250 TP per processor core" isn't it? :)

(I couldn't help myself. I tried. I really did. I just couldn't resist.)

I have wondered for a while, and I suspect you would know, does a
Hyperthreaded processor count as a processor core for this? I've always
assumed it did, but I could really see it either way.
 
Peter Duniho said:
2) It shouldn't break anything else, including anything assuming
details of what gets run on a threadpool thread.

No. As I pointed out, it could easily break an application that assumes
it can send thousands of work items to the thread pool without any real
problems. [...]

Well, only if those items are queued for a long time - don't forget
that the ThreadPool still has a "ramp up" time before it creates new
threads. Queuing 250 items won't start 250 threads unless they're there
for a really long time. (I think it starts up at most one thread a
second, or at least something of that order.)

I've never disputed that it _reduces_ the chances of the problem
happening. But it doesn't eliminate it. You can show all sorts of
reasons it becomes less likely, but none of those are things that prevent
the problem altogether. A different, more correct fix would _eliminate_
the problem.

By the way, the last time I looked the start-up interval was 2 threads per
second. So 250 items would only take a couple of minutes to get all those
threads started. There are lots of examples of types of processing in
which a couple of minutes is a relatively small portion of the overall
work.

But even if it took five, ten, twenty minutes to start the maximum number
of threads, that doesn't rule out a problem. In fact, in the deadlock
example all it means is that it takes longer to reach the actual
deadlocked situation. Code waiting on a queued thread pool work item
waits regardless of the reason for there not being an available thread.
The code can wind up essentially deadlocked right away; it just doesn't
"know" it yet.

Pete
 
Chris Mullins said:
Of course. That'll teach me (again!) to not proofread my posts a 3rd or
4th time.
Happens to me all the time, watch out ;-)
You mean "250 TP per processor core" isn't it? :)
Processor cores in case of muti-core architectures, or processors in case of
single core legacy archtectures.
(I couldn't help myself. I tried. I really did. I just couldn't resist.)

I have wondered for a while, and I suspect you would know, does a
Hyperthreaded processor count as a processor core for this? I've always
assumed it did, but I could really see it either way.

Yes it does, although it better didn't :-)

Willy.
 
[...]
I have wondered for a while, and I suspect you would know, does a
Hyperthreaded processor count as a processor core for this? I've always
assumed it did, but I could really see it either way.

Yes it does, although it better didn't :-)

I'll second that. I learned the hard way that while hyperthreading can
help somewhat for multi-process situations, and can even help a little for
certain multi-thread situations, it's very easy for a hyperthreading
processor to perform _much_ worse with multiple threads than with just
one. :(

Pete
 

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

Back
Top