Why does Thread class not support IDisposable?

  • Thread starter Thread starter Creativ
  • Start date Start date
C

Creativ

Why does Thread class not support IDisposable? It's creating quite
some problem. Namely, it can exhaust the resource and you have not
control over it.
 
Why does Thread class not support IDisposable?

Why should it? What would Thread.Dispose actually do?
It's creating quite some problem. Namely, it can exhaust the resource and you
have not control over it.

I've done a fair amount of threading and never got into a situation
where I want to dispose of a thread. Now terminating a thread in a
graceful manner is a different matter - but that's up to the
collaboration between the threads. If you need to "hard" reset a
thread, Abort is your friend - but you should only really do that if
you're tearing down the process (or at least the AppDomain) as it
leaves things in an indeterminate state.

Jon
 
The scenario I can think of is you create a thread to run some task.
After some runs, quite some threads will be created. After doing that
for a long time, you will have problem in creating Thread and run it
since the CloseHandle will be called in the Finalizer.
 
The scenario I can think of is you create a thread to run some task.
After some runs, quite some threads will be created. After doing that
for a long time, you will have problem in creating Thread and run it
since the CloseHandle will be called in the Finalizer.

If you've got that many actual threads, you've got bigger problems
anyway. Have you actually run into this as a problem? Have you seen
anyone else running into this as a problem?

Jon
 
Yes, I did. Some one created a lot of thread to run some calculation.
Finally he get an error from the numeric library, "Reached Thread
Limit".
I adviced him to use ThreadPool.
But idealy, Thread should support IDisposable since it's a limited
resource.
 
Yes, I did. Some one created a lot of thread to run some calculation.
Finally he get an error from the numeric library, "Reached Thread
Limit".

But did you isolate the problem to finalization rather than it
physically trying to run too many threads?
I adviced him to use ThreadPool.
But idealy, Thread should support IDisposable since it's a limited
resource.

But it's not something that *can* be manually released. There's the
handle, but that's it - and I suspect that's not what you were seeing
in the above situation.

As I said before, if you're creating that many threads you will run
into issues regardless.

Jon
 
As I said before, if you're creating that many threads you will run
into issues regardless.
The problem is as following. Since the Finalize() can be really
delayed, the program will hold a lot of thread handle when those
theads are ready. Implementing a Dispose which call CloseHandle() will
solve this problem.
 
The problem is as following. Since the Finalize() can be really
delayed, the program will hold a lot of thread handle when those
theads are ready. Implementing a Dispose which call CloseHandle() will
solve this problem.

Only if you know exactly when to call it - which you typically don't.

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.

It's like exceptions: there's a slight performance hit, but if you're
throwing enough to see a *significant* issue, then you're almost
certainly misusing exceptions in the first place.

Jon
 
Creativ said:
Changing to ThreadPool, you won't get that kind problem.

Of course not, because in this case no new threads are created but the
old ones are recycled. From a performance point of view, this is an
additional benefit of using the ThreadPool, as creating new threads can
be a quite expensive operation (DllMain calls, etc.).

Best regards,
Martin
 
Creativ said:
Yes, I did. Some one created a lot of thread to run some calculation.
Finally he get an error from the numeric library, "Reached Thread
Limit".

I've gotta agree with Jon on this one. It's not a limitation in Windows
Threading, or the implementation of .Net thread constructs. The problem here
is the algorithm being used is deeply flawed.
I adviced him to use ThreadPool.
But idealy, Thread should support IDisposable since it's a limited
resource.

Threads are special. There's no way an external source can Dispose a thread.
It just isn't something that makes logical sense given the design of threads
in Windows.

An external source can set a flag, "Please Exit when you can", and the
thread can choose to honor that flag. Such a construct is common.
 
Yes, I did. Some one created a lot of thread to run some calculation.
Finally he get an error from the numeric library, "Reached Thread
Limit".
I adviced him to use ThreadPool.

And did that solve the problem? Can you post a sample of code that
demonstrates this "Reached Thread Limit" error, as well as an example of
how using ThreadPool avoids the problem?

I agree with Jon: this isn't an issue that IDisposable would address.
There's nothing for the Thread instance to dispose. If you are reaching
some limit on the number of threads, it's because the threads you've
already started haven't exited yet. Even assuming that Thread did
implement IDisposable, it would be a serious problem to dispose the object
before you're done with it (that is, before the thread has finished).

The solution to your friend's problem is to not start new threads before
the previously started ones have exited.

Using the ThreadPool might work around the issue, but only because it
won't allow additional new threads to begin execution as long as the
previously started threads haven't finished. Assuming that the original
bug causing the previously started threads to not finish isn't fixed, then
all that using ThreadPool will do is hide the "Reached Thread Limit" error
with a "my processing never completes" bug, or even an out-of-memory error
(eventually, the queue of threads will get so large that the program runs
out of memory...it might take awhile, maybe a very long while depending on
how the threads are started, for this to happen though).

Just because the "Reached Thread Limit" error goes away when you use
ThreadPool, that doesn't mean you've fixed your bug.
But idealy, Thread should support IDisposable since it's a limited
resource.

Being a "limited resource" isn't what defines whether something implements
IDisposable or not.

Memory is a limited resource. A simple Array object consumes this limited
resource. Yet, there's absolutely no reason for Array to implement
IDisposable, and implementing IDisposable on Array would not in any way
get around exhausting the limited resource through use of an Array.

The bottom line here: reaching the maximum number of threads is
representative of a bug in the code using the Thread class, and is _not_
representative of a defect in the design of the Thread class.

If you can post some sample code that demonstrates the problem, we'll be
able to point out the error in the code that's leading to the problem.
The "problem" being described is a non-problem, but we can at least offer
some advice as to how not to write buggy code. :)

Pete
 
Of course not, because in this case no new threads are created but the
old ones are recycled.

Not if the basic logic causes the application to run out of threads
without ThreadPool. In that case, without a change to that basic logic,
using a thread pool the old ThreadPool threads aren't recycled because the
original delegate executing in the thread won't exit.

Instead, you just an ever-growing queue of new work items for the thread
pool.
From a performance point of view, this is an
additional benefit of using the ThreadPool, as creating new threads can
be a quite expensive operation (DllMain calls, etc.).

It's true, using a thread pool can be a nice way to handle certain
aysynchronous operations. But if you've got a bug with regular threads,
you've still got a bug using thread pool threads.

Pete
 
Peter Duniho said:
Instead, you just an ever-growing queue of new work items for the thread
pool.

Yes, I completely agree with you. In my opinion it is exactly this queue
which will "solve"/hide the error, as there are never "too many"
threads, as the ThreadPool won't grow beyond its maximum thread count.
It's true, using a thread pool can be a nice way to handle certain
aysynchronous operations. But if you've got a bug with regular threads,
you've still got a bug using thread pool threads.

Again, agreed. If the problem is that there are too many concurrent
requests, though, the TreadPool _could_ be the solution, as, as you
mentioned, the work items get queued instead of spawning a new thread.

Best regards,
Martin
 
You're still going to have problems.

If you've got all of the threadpool threads busy, you may end up deadlocking
the entire threadpool.

The think that keeps being danced around, is that a solution like this
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.

Abuse of threads, be they custom threads, threadpool threads, IOCP threads,
or some new type I'm not aware of, all hurts performance and is an indicator
of a fundamentaly flawed desing.

.... 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.
 
You're still going to have problems.
Always?

If you've got all of the threadpool threads busy, you may end up
deadlocking
the entire threadpool.

Or it may exactly the right thing to do. It just depends.
The think that keeps being danced around, is that a solution like this
performance waaaaay worse than a single-threaded soultuion.

A solution like what? In all cases?
You're using all
system resources creating and scheduling threads. Almost no work is being
done towards your actual problem set.

How do you know that for sure? I haven't seen any specifics in this
thread regarding what "work" is being done. How could we possibly know
that "almost no work" is being done? At the very least, up to the same
number of threads as CPU cores, if the work can be done concurrently in a
thread pool then performance should definitely improve. For work that is
primarily i/o, you could easily exceed the number of CPU cores, and even
consume the entire thread pool (*), while still enjoying a performance
improvement.

(*) Of course, completely consuming the thread pool interferes with other
uses of the thread pool. I'm assuming an abstract case where the "thread
pool" in question is dedicated to the problem at hand.
Abuse of threads, be they custom threads, threadpool threads, IOCP
threads,
or some new type I'm not aware of, all hurts performance and is an
indicator
of a fundamentaly flawed desing.

_Abuse_ of threads, yes. But using a thread pool generally? Even if the
entire thread pool winds up busy at some point? That's impossible to say
without knowing more about the work being done.
... 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, if you have 1000 chunks of work, and you're using a thread pool, I
see no reason at all to not just point 1000 of those chunks at the thread
pool and let it go. The queueing in the thread pool is supposed to manage
the number of actual concurrent threads. For i/o, a number much larger
than the number of CPU cores is likely to be fine, and for CPU-bound stuff
of course you might want a dedicated thread pool that restricts the number
of active threads to the number of CPU cores. But even in that case,
enqueuing all 1000 chunks at once seems fine to me.

For that matter, I guess I don't really know for sure that in the case of
the problem described in this thread that a thread pool isn't an
appropriate solution. The original problem seems to describe a situation
where thread instances are added gradually, eventually reaching the
maximum number. But if a large number are added all at once, and over
time the thread procedures do exit and on average more threads are not
created than can complete, using a thread pool might in fact be a good
solution to the OP's problem.

But even in that case, it's not because Thread should implement
IDisposable. :)

Pete
 
Peter Duniho said:

Probably not always, but certainly 'often'. It's going to depend on what
kind of work you're posting to the threadpool. Given that some kinds of work
will kill it, and other won't, and it hard to predict which is which, that
puts it squarely in the "don't use it" category.

The problem (as we both know) is doing I/O from a threadpool thread, risks
deadlock. It's not even I/O - but anything that requires a callback, which
in turn requries a threadpool, which isn't available.
A solution like what? In all cases?

A solution that creates so many threads the O/S comes back and says, "No
more threads!". This likley implies thousands of threads.
How do you know that for sure?

Well, if so many threads exist that the O/S is unable to create more
threads, then it's so busy scheduling the existing threads that nothing is
going to get done. I suppose you could muck with priorities to get around
that, but... yea, I feel pretty confidant about that! :)
At the very least, up to the same number of threads as CPU cores, if the
work can be done concurrently in a thread pool then performance should
definitely improve.

Totally, with ya 100%. But with an error described as, "Finally he get an
error from the numeric library, "Reached Thread Limit"." then I'm thinking
thousands and thousands of threads. I could certainly be wrong, but that's
my assumption.
For work that is primarily i/o, you could easily exceed the number of CPU
cores, and even consume the entire thread pool (*), while still enjoying
a performance improvement.

Heh. Been there. Done that.

.... by mistake in a few cases, much to my chagrin.

me: !threads
sos: "Listing all 1050 threads"
me: "wow. I can't be reading that right."

<silence/>

me: "Oh. that would explain it."
_Abuse_ of threads, yes. But using a thread pool generally? Even if the
entire thread pool winds up busy at some point? That's impossible to say
without knowing more about the work being done.

Well, with the growth of the thread pool to 250 threads per core, that means
up to 1000 threads active on a quad-core system.

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

These numbers really scare me away from the thread pool...
But even in that case, it's not because Thread should implement
IDisposable. :)

Hey, no drifting back to the original topic, that's bad UseNet form!
 
[...]
A solution like what? In all cases?

A solution that creates so many threads the O/S comes back and says, "No
more threads!". This likley implies thousands of threads.

But the point of the thread pool is that you're not actually creating new
threads. I read your post to suggest that even using a thread pool
"you're still going to have problems". To me, the whole point of having a
thread pool is not only to provide an easy way to assign tasks to separate
threads, but also to have the queuing behavior to ensure that you don't
have too many active threads at once.

If the built-in thread pool doesn't provide this (see below), then one can
create their own thread pool. It seems to me that generally speaking, the
use of a thread pool _can_ in fact avoid some problems. Will it actually
fix the problem here? Not sure. But I don't think it's true that even
using a thread pool you're necessarily going to have problems. Depending
on the algorithm being implemented, a thread pool could in fact solve
whatever basic architectural problem exists, without introducing new ones.

It might not, but I don't think you can say that it always won't.
Well, if so many threads exist that the O/S is unable to create more
threads, then it's so busy scheduling the existing threads that nothing
is
going to get done.

That assumes that you're creating a new thread for each task item though.
The point of the thread pool comments is that it can avoid this, by
ensuring that the number of actual threads created is well below the OS
limit, and within a reasonable number of concurrently active threads.

Again, I read your comment to mean that even using the thread pool you'd
have the same problems as if you created a new thread for each task item.
That's what I'm wondering about.

By the way, as long as the threads are CPU-bound, I don't actually believe
that scheduling overhead will prevent work from getting done. Unless a
thread blocks, it's going to use up the roughly 50ms quantum given to it,
and a context switch is a _MUCH_ smaller amount of time than that. The
biggest problem in a scenario like that is the potential for causing
frequent cache misses on a CPU architecture that has a huge dependency on
the cache to perform well. Another problem is that properly managed, you
could avoid the context switch altogether so any context switching
represents some reduction in performance. Yet another problem is that if
all the tasks are run concurrently, then you don't get any results until,
right at the end, you get all the results. The latter problem is mainly
one of user responsiveness; it doesn't relate to overall throughput.

The context switch is a performance issue, granted...but it's not like
having even more threads makes it worse. Once you have twice as many
threads as CPU cores, you guarantee that each thread will have to give up
the CPU at the end of its timeslice. More threads doesn't make that any
worse. And while I don't have the numbers for the cost of a
context-switch off the top of my head, I would be surprised if it took
more than 1% of the timeslice, and surely it's not more than 10%. If
anything, I suspect that the reason a timeslice is so large still is that
otherwise the context switch _would_ be a large percentage of the time.

So, context switching causes overhead? Yes, no question. But is it a
disaster? I've got no reason to believe it has to be. Context switching
alone isn't going to cause it to be, and that's the only real constant.
Trashing the cache is a big potential problem as well, but that can be
avoided, whether by design or simply having computational tasks that don't
need a lot of memory and don't experience cache aliasing. Also caches are
pretty huge these days, which helps a lot.
[...]
Totally, with ya 100%. But with an error described as, "Finally he get an
error from the numeric library, "Reached Thread Limit"." then I'm
thinking
thousands and thousands of threads. I could certainly be wrong, but
that's
my assumption.

Sure, thousands and thousands of threads in the original implementation.
But not with a thread pool.
[...]
Well, with the growth of the thread pool to 250 threads per core, that
means
up to 1000 threads active on a quad-core system.

Really? The original default was, what? 25 threads? They've upped that
to 250 threads? A 10X increase?

This is where I become concerned that the default thread pool doesn't
provide an appropriate level of CPU management.

Note also that in 32-bit Windows, there's by default a practical limit of
about 2000 threads per process (and that doesn't even account for other
memory consumption of the process), even ignoring the context switching
overhead. Given that the IOCP thread pool is by default 1000 threads,
that means that on a 4-core system those two thread pools alone basically
could consume the entire quota for a process, if they were both utilized
to their maximum capacity. When they dramatically increased the number of
threads in the thread pool, did they also reduce the stack size for each
thread?

But even assuming that they've messed up the thread pool in this way, one
could easily just create their own, or even just change the default
maximum to better suit the application's needs and behavior (i.e. put it
back to 25 like it was).

I still don't see how using thread pools _guarantees_ the same problems as
the original "create thousands of threads" implementation.

Well, I agree with you that 250/CPU is way too many for the thread pool.
I also think the justification (avoiding thread pool deadlock) is lame.
So they're going to trash the behavior of nominally well-behaved
applications just as a work-around to accomodate badly-behaved
applications? Ick.

Even his example seems lame to me; no competent programmer ought to be
writing code like that, and if they do they _deserve_ to get deadlocked.
So there!

And raising the thread pool number of threads doesn't make the problem go
away; it just delays it. His example only requires sorting an array of
30-some elements to deadlock (according to him...I haven't bothered to
think about why his number is the magic number, but it sounds about right
for a 25-thread pool), so even expanding the thread pool by a factor of
10X doesn't seem likely to me to change the problem enough to avoid having
it happen in real-world situations.

Personally, I think it ought to be an exception for code running in a
thread pool thread to intentionally block at all (that is, just block for
the sake of blocking...I'd grant some leeway on blocking because of i/o,
but only if that i/o didn't involve direct user input). The whole point
of a thread pool is that the things you run on it are reasonably short and
don't sit around waiting, on other threads or on the user. Calls to
Join(), WaitOne(), Sleep(), etc. should just be prohibited.

Crazy? Maybe. But I think if we're going for crazy solutions to bad
programmers, I'd rather just make it painful for them right away, rather
than to just try to sweep their bugs under the rug.

Sorry...I suppose I could be preaching to the choir on this question about
the 250/CPU max issue.
Hey, no drifting back to the original topic, that's bad UseNet form!

Sorry. I forgot the rule of primary Usenet entropy. :)

Pete
 
Let me elaborate my question more. When the execution is ready, when
CloseHandle will be called?
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.
 
Chris said:
I've gotta agree with Jon on this one. It's not a limitation in Windows
Threading, or the implementation of .Net thread constructs. The problem here
is the algorithm being used is deeply flawed.


Threads are special. There's no way an external source can Dispose a thread.
It just isn't something that makes logical sense given the design of threads
in Windows.

An external source can set a flag, "Please Exit when you can", and the
thread can choose to honor that flag. Such a construct is common.

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?
 

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