Dear developers, I'm still a little ignorant to real threads. In order to do the implementation of hard-wired microthreads right, I tried to understand how real threads work. My question, which I could not easily answer by reading the source is: What happens when the main thread ends? Do all threads run until they are eady too, or are they just killed away? And if they are killed, are they just removed, or do they all get an exception for cleanup? I would guess the latter, but I'm not sure. When a thread ends, it may contain several levels of other C calls which might need to finalize, so I thought of a special exception for this, but didn't find such. Many thanks and sorry about my ignorance - chris -- Christian Tismer :^) <mailto:tismer@tismer.com> Mission Impossible 5oftware : Have a break! Take a ride on Python's Kaunstr. 26 : *Starship* http://starship.python.net/ 14163 Berlin : PGP key -> http://wwwkeys.pgp.net/ PGP Fingerprint E182 71C7 1A9D 66E9 9D15 D3CC D4D7 93E2 1FAE F6DF where do you want to jump today? http://www.stackless.com/
[Christian Tismer]
... My question, which I could not easily answer by reading the source is: What happens when the main thread ends? Do all threads run until they are ready too, or are they just killed away?
You're walking near the edge of a very steep cliff. There are jagged rocks a kilometer below, so don't slip <wink>. It varies by OS, and even by exactly how the main thread exits. Reading OS docs doesn't really help either, because the version of threads exposed by the C libraries may differ from native OS facilities in subtle but crucial ways.
And if they are killed, are they just removed, or do they all get an exception for cleanup?
Can only be answered one platform at a time. They're not going to get a *Python*-level exception, no. Here's a simple test program: import thread import time def f(i): while 1: print "thread %d about to sleep" % i time.sleep(0.5) for i in range(3): thread.start_new_thread(f, (i,)) time.sleep(3) print "main is done" and a typical run on Windows: C:\Code\python\PCbuild>\python22\python.exe tdie.py thread 0 about to sleep thread 1 about to sleep thread 2 about to sleep thread 0 about to sleep thread 1 about to sleep thread 2 about to sleep thread 0 about to sleep thread 1 about to sleep thread 2 about to sleep thread 0 about to sleep thread 1 about to sleep thread 2 about to sleep thread 1 about to sleep thread 0 about to sleep thread 2 about to sleep thread 1 about to sleep thread 0 about to sleep thread 2 about to sleep thread 1 about to sleep main is done C:\Code\python\PCbuild> I expect much the same on Linux (all threads die, no exceptions raised). But, IIRC, the threads would keep going on SGI despite that the main thread is history.
... When a thread ends, it may contain several levels of other C calls which might need to finalize, so I thought of a special exception for this, but didn't find such.
Closing threads cleanly is the programmer's responsiblity across all OSes. It can be very difficult. Python doesn't really help (or hinder). Microsoft helps in that DLLs can define a "call on thread detach" function that's automatically called when a thread detaches from the DLL, but Python doesn't exploit that. The DLL hook may not get called even if it did, depending on exactly how a thread detaches (the Big Hammer last-chance Win32 TerminateProcess/TerminateThread functions generally leave things a mess -- "TerminateThread is a dangerous function that should only be used in the most extreme cases", etc).
Tim Peters wrote:
[Christian Tismer]
... My question, which I could not easily answer by reading the source is: What happens when the main thread ends? Do all threads run until they are ready too, or are they just killed away?
You're walking near the edge of a very steep cliff. There are jagged rocks a kilometer below, so don't slip <wink>.
Uhmm -- I really didn't want to poke into something problematic, but obviously I have no more simple questions left. ;-)
It varies by OS, and even by exactly how the main thread exits. Reading OS docs doesn't really help either, because the version of threads exposed by the C libraries may differ from native OS facilities in subtle but crucial ways.
It does not sound like being designed so, more like just some way through these subtleties, without trying to solve every platform's problems. I don't try to solve this, either. But since I'm writing some kind of platform independant threads (isn't it funny? by using non-portable tricks, I get some portable threads), I'd like to think about how this world *could* look like. Maybe I have a chance to provide an (u)thread implementation which is really what people would want for real threads?
And if they are killed, are they just removed, or do they all get an exception for cleanup?
Can only be answered one platform at a time. They're not going to get a *Python*-level exception, no. Here's a simple test program:
[thanks for the test code]
I expect much the same on Linux (all threads die, no exceptions raised). But, IIRC, the threads would keep going on SGI despite that the main thread is history.
So threads do force the programmer to write platform-dependant Python code. For sure nothing that Python wants, it just happens.
... When a thread ends, it may contain several levels of other C calls which might need to finalize, so I thought of a special exception for this, but didn't find such.
Closing threads cleanly is the programmer's responsiblity across all OSes. It can be very difficult. Python doesn't really help (or hinder).
Ok with me, this is really not trivial. (I guessed that from reading the source, but it really was not obvious. So I asked a naive question, but you know me better...) Maybe Python could try to help though an API?
Microsoft helps in that DLLs can define a "call on thread detach" function that's automatically called when a thread detaches from the DLL, but Python doesn't exploit that. The DLL hook may not get called even if it did, depending on exactly how a thread detaches (the Big Hammer last-chance Win32 TerminateProcess/TerminateThread functions generally leave things a mess -- "TerminateThread is a dangerous function that should only be used in the most extreme cases", etc).
Now the real question: If you have the oportunity which I have: Define some threads which (mis)behave equally (un)well on every supported platform, once and forever. Would you try to mimick the median real threads behavior as they work today? Or would you try to build something consistent, cross-platform, that makes sense, that would even make sense for new revisions of the real thread modules? I think here is a chance to do a reference implementation of (u)threads since there are absolutely no OS dictated restrictions or MS added doubtful features, we can just do it right. Given that there is a suitable definition of "right", of course. The problem is that I'm not a specialist on threading, therefore I'm asking for suggestions. Please, what do you all think would be "right", given that you have full control of ver your "virtual OS"? contructively-but-trying-not-to-overdo - ly y'rs - chris -- Christian Tismer :^) <mailto:tismer@tismer.com> Mission Impossible 5oftware : Have a break! Take a ride on Python's Kaunstr. 26 : *Starship* http://starship.python.net/ 14163 Berlin : PGP key -> http://wwwkeys.pgp.net/ PGP Fingerprint E182 71C7 1A9D 66E9 9D15 D3CC D4D7 93E2 1FAE F6DF where do you want to jump today? http://www.stackless.com/
Christian Tismer wrote:
I don't try to solve this, either. But since I'm writing some kind of platform independant threads (isn't it funny? by using non-portable tricks, I get some portable threads), I'd like to think about how this world *could* look like. Maybe I have a chance to provide an (u)thread implementation which is really what people would want for real threads?
No, you don't. Real threads have one killer advantage you just can't emulate: they can parallelize I/O operations (and theoretically parallelize computations on multiple CPUs). The advantage of microthreads has been that they're lightweight, so they're good for applications that require *lots* of threads, such as simulations. I think keeping this advantage would be a Good Idea. You might want to look at Ruby, though, because it does what you're wanting to do. (I think -- I haven't touched Ruby myself.) -- --- Aahz (@pobox.com) Hugs and backrubs -- I break Rule 6 <*> http://www.rahul.net/aahz/ Androgynous poly kinky vanilla queer het Pythonista We must not let the evil of a few trample the freedoms of the many.
If you ask Guido, the only reason to use threads is to do overlapped I/O. And if you come up with a good counter-example, he'll find a way to *call* it overlapped I/O, so there's no opposing him on this <wink>. That's clearly a huge reason in practice to use threads, and that reason requires using platform threads (to get true overlap). Another huge reason is to play nice with threaded libraries written in other languages, and again that requires playing along with platform threads. So what most thread users want is what Python gives them: a thin wrapper around native threads, complete with platform quirks. The threading.py module adds *some* sanity to that, providing portable APIs for some important synch primitives, and uniform thread shutdown semantics (as Guido pointed out, when you use threading.py's thread wrappers, when the main thread exits it waits for all (non-daemon) threads to quit). What people seem to ask for most often now is a way for one thread to tell another thread to stop. In practice I've always done this by having each thread poll a "time to stop?" variable from time to time. That's not what people want, though. They want a way to *force* another thread to stop, even if (e.g.) the target thread is stuck in a blocking read, or in the middle of doing an extraordinarily expensive regexp search. There simply isn't a portable way to do that. Java initially spec'ed a way to do it in its thread model, but declared that deprecated after obtaining experience with it: not only did it prove impossible to implement in all cases, but even when it worked, the thread that got killed had no way to leave global invariants in a sane state (e.g., the thread may have had any number of synch gimmicks-- like locks --in various states, and global invariants for synch gimmicks can't tolerate a participant vanishing without both extreme care and a way for a thread to declare itself unstoppable at times). So that's a mess, but that's still what people want. OTOH, they won't want it for long if they get it (just as Java ran screaming from it). I'm not sure the audience for cororoutine-style threads even overlaps. You could try to marry both models, by running coroutine-style threads in a pool of OS threads. Then, e.g., provided you knew a potentially blocking I/O call when you saw one, you could farm it out to one of the real threads. If you can't do that, then I doubt the "real thread" crowd will have any interest in uthreads (or will, but treat them as an entirely distinct facility -- which, for their purposes, they would be). For purposes of computational parallelism (more my background than Guido's -- the idea that you might want to use a thread to avoid blocking on I/O was novel to me <wink>), the global interpreter lock renders Python useless except for prototyping, so there's not much point digging into the hundreds of higher-level parallelism models that have been developed. IOW, uthreads are their own universe. I haven't used them, so don't know what would be useful. What do the current uthread users ask for? That's where I'd start.
Tim Peters wrote:
For purposes of computational parallelism (more my background than Guido's -- the idea that you might want to use a thread to avoid blocking on I/O was novel to me <wink>), the global interpreter lock renders Python useless except for prototyping, so there's not much point digging into the hundreds of higher-level parallelism models that have been developed.
Well, maybe. I'm still hoping to prove you at least partly wrong one of these years. ;-) (The long-term plan for my BCD module is to turn it into a C extension that releases the GIL. If that's successful, I'll start working on ways to have Numeric release the GIL.) -- --- Aahz (@pobox.com) Hugs and backrubs -- I break Rule 6 <*> http://www.rahul.net/aahz/ Androgynous poly kinky vanilla queer het Pythonista We must not let the evil of a few trample the freedoms of the many.
[Tim]
For purposes of computational parallelism ... the global interpreter lock> renders Python useless except for prototyping, so there's not much point digging into the hundreds of higher-level parallelism models that have been developed.
[Aahz]
Well, maybe. I'm still hoping to prove you at least partly wrong one of these years. ;-)
WRT higher-level parallelism models, you already have in a small way, by your good championing of the Queue module. Queue-based approaches are a step above the morass of low-level home-grown locking protocols people routinely screw up; it's almost *hard* to screw up a Queue-based approach. The GIL issue is distinct, and it plainly stops computational parallelism from doing any good so long as we're talking about Python code.
(The long-term plan for my BCD module is to turn it into a C extension that releases the GIL.
Well, that's not Python code. It's unclear whether it will actually help: Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS aren't free, and a typical BCD calculation may be so cheap that it's a net loss to release and reacquire the GIL across one. Effective use of fine-grained parallelism usually requires something cheaper to build on, like very lightweight critical sections mediating otherwise free-running threads.
If that's successful, I'll start working on ways to have Numeric release the GIL.)
I expect that's more promising because matrix ops are much coarser-grained, but also much harder to do safely: BCD objects are immutable (IIRC), so a routine crunching one doesn't have to worry about another thread mutating it midstream if the GIL is released. A Numeric array probably does have to worry about that.
My question, which I could not easily answer by reading the source is: What happens when the main thread ends? Do all threads run until they are eady too, or are they just killed away? And if they are killed, are they just removed, or do they all get an exception for cleanup?
If you're talking about the thread module, they are killed without being given notice. The threading module however waits for all non-daemon threads, using the atexit mechanism build on top of sys.exit. --Guido van Rossum (home page: http://www.python.org/~guido/)
Christian Tismer wrote:
I'm still a little ignorant to real threads. In order to do the implementation of hard-wired microthreads right, I tried to understand how real threads work.
Can't answer your specific question, but you might want to look at my Starship pages if you want to increase your general understanding of Python threads (there probably won't be much new to you; OTOH, it shouldn't take you long to read). http://starship.python.net/crew/aahz/ -- --- Aahz (@pobox.com) Hugs and backrubs -- I break Rule 6 <*> http://www.rahul.net/aahz/ Androgynous poly kinky vanilla queer het Pythonista We must not let the evil of a few trample the freedoms of the many.
Aahz Maruch wrote:
Christian Tismer wrote:
I'm still a little ignorant to real threads. In order to do the implementation of hard-wired microthreads right, I tried to understand how real threads work.
Can't answer your specific question, but you might want to look at my Starship pages if you want to increase your general understanding of Python threads (there probably won't be much new to you; OTOH, it shouldn't take you long to read).
Oh well, 1024 thanks, very helpful. I'm again the clueless implementor. It still feels warm and fuzzy here, although I think there is no rule that I missed to break since -dev knows about me, and now my final sacrileg... ...after all, this is kinda piecemaker, since Stackless is now orthogonal, in a way. I gave up some academic POV, in favor of something pragmatic, and finally we all get rid of a problem. Hey, I want to become a productive contributor (again?) :) thanks - chris -- Christian Tismer :^) <mailto:tismer@tismer.com> Mission Impossible 5oftware : Have a break! Take a ride on Python's Kaunstr. 26 : *Starship* http://starship.python.net/ 14163 Berlin : PGP key -> http://wwwkeys.pgp.net/ PGP Fingerprint E182 71C7 1A9D 66E9 9D15 D3CC D4D7 93E2 1FAE F6DF where do you want to jump today? http://www.stackless.com/
participants (4)
-
aahz@rahul.net
-
Christian Tismer
-
Guido van Rossum
-
Tim Peters