[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: Threads



Hi,
	[Sorry, I tend to get carried away on threads]

>>"Tom" == Tom Lees <tom@lpsg.demon.co.uk> writes:

Tom> On Mon, Oct 20, 1997 at 05:21:14PM -0500, Manoj Srivastava wrote:
>> Hi, "Bruce" == Bruce Perens <bruce@pixar.com> writes:

>> In any case, I don't think we can code without threads now, and add
>> threads later.

Tom> This all depends on the degree of threaded-ness. Adding threads
Tom> later may be fairly easy, if discarding ~10% of current code to
Tom> do so.

	This causes alarums to go off in my head. I have seen a lot of
 peoducts being re-engineered to be distributed/multithreaded, and in
 the majority of them it was less costly to redesign/rewrite. 

	Good multi-threaded code is as much of a paradigm shift as OO
 methodology is. Would you say that a procedural program can be made
 OO later on? 

Tom> The underlying engine in this case would not be much
Tom> different. But it would be a good idea to make the UI/widget-set
Tom> side of things thread-safe now, as that would probably need
Tom> larger changes.

	Thread safe is simple; just wrap all code at method level into
 a common critical region; bingo, thread safe, and inefficient.

Tom> But, there are a few issues with using threads:-

Tom> libc5 doesn't handle threads well. This will limit us to a libc6
Tom> implementation only. This probably won't affect us, though.

	Right.

Tom> POSIX threads work nicely, _BUT_ they don't handle signals
Tom> particularly well (LinuxThreads doesn't do POSIX, other
Tom> implementations do). And, the big question is, what about
Tom> libslang, libgpm, etc, etc. Are they thread-safe yet?

	Huh? Why do you say that? The programmer has to understand the
 difference between type of signals, and one has to set up a signal
 handler thread (and *not* muck with the per thread sig mask unless
 one bloody well knows what one is doing). Unfortunately, a lot of
 programmers start out by writing their code under the mistaken
 assumption that they can set a signal handler for each thread; this
 is not the way things work. You can _block_ or _unblock_ signals on a
 thread-by-thread basis, but this is not the same thing.

	As of draft 10, there is one vector of handlers shared by all
 threads in the process (i.e., the old behavior for "asynchronous"
 signals).  Thus, if any thread changes the signal action for a given
 signal, all threads are affected.
	
	While we often casually talk of "asynchronous" and
 "synchronous" signals, there is really no such concept in
 POSIX. There are some differences in behavior based on HOW the signal
 was generated, and the terminology results from the fact that some
 signals are traditionally generated "synchronously" while others are
 traditionally generated "asynchronously". Still, although SIGSEGV is
 a "synchronous" signal, if you "kill -SEGV <pid>", the delivery (and
 therefore, the BEHAVIOR) of the resulting signal is asynchronous.

	Signal ACTIONS (not just handlers, but all the struct
 sigaction fields) are shared by all threads in a process. There is no
 such thing as a per-thread signal action in POSIX. Each thread has a
 unique signal mask, and a unique set of pending signals -- but that's
 all. (And the set of signals that a thread might receive are
 determined not only by the thread's set of pending signals, but also
 by the set of signals pending against the process.)

	Finally, in order for ANY thread to reliably utilize sigwait()
 (or its realtime cousins sigwaitinfo() and sigtimedwait()), ALL
 threads in the process must have the signal(s) masked. Otherwise the
 signal may be delivered to another thread rather than the
 sigwaiter. (Although most implementations will favor a sigwaiter over
 another thread with the signal unmasked, that's not required by the
 standard -- and even if it were required, the signal would still go
 to another thread if the signal recurs occurs while the sigwaiter is
 processing one signal prior to waiting for the next.)

	A "synchronous" signal (hardware trap) caused by the current
 thread CANNOT be delivered to any other thread, under any
 circumstances. That would be a violation of POSIX rules. (Of course,
 if the implementation doesn't care to conform to POSIX, it can do
 anything it pleases -- but any thread package that does such a thing
 by default [without requiring nonportable changes to the application
 code] is severely nonconforming and most definitely should not be
 pretending to be "Pthreads".)

	Asynchronous signals are not NECESSARILY delivered to the
 currently running thread. POSIX says only that it will be delivered
 to one (and only one) thread in the process, if there are any threads
 that do not have the signal masked. While most implementations will
 probably choose the currently running thread, if eligible, that is
 not required -- and you should therefore never depend upon that
 assumption!
 
	Further, if the current thread is not eligible, the
 implementation cannot simply hold it pending -- unless NO thread is
 eligible to receive the signal. (In which case it will pend against
 the process, until some thread unmasks the signal, or sigwaits for
 it.)


Tom> SLang could turn out to be a problem with the signals, because it
Tom> will need to handle SIGWINCH. If the signals are delivered to one
Tom> thread only, it will work without necessarily needing to be
Tom> thread-safe. You just need to put the UI into the "main" threads
Tom> (i.e. the one that spawns all the others).

	If SIGWINCH is masked in all generated threads, then the
 signal shall only be delivered to the one thread that has unmasked
 SIGWINCH and is blocked in a sigwait. One should not install signal
 handlers. 

	The problem with signal handlers, of course, is that you are
 forbidden to use ANY pthread interfaces from within a signal
 handler. That includes not only pthread_cond_wait and
 pthread_cond_signal but also pthread_mutex_lock and
 pthread_mutex_unlock.

	Mutexes and condition variables are specifically NOT required
 to be "interrupt reentrant", for a lot of excellent reasons. POSIX
 semaphores are, on the other hand, required to have (limited)
 interrupt reentrancy; in specific, it is legal to call sem_post() from
 a signal handler.


Tom> If you try this under a _TRUE_ POSIX implementation (i.e. signals
Tom> sent to _ALL_ the threads), there may be large reentrancy
Tom> problems which occur, and this hurts portability.

	If you think about this a moment, you'll realize that any
 programmer coding for POSIX threads would have had to evolve
 solutions for this (indeed, the standard would have been pretty
 stupid not to have considered this in the design phase). 

Tom> I would say, if the libraries we are using can handle it, go for
Tom> using threads, as long as you use them sparingly. Don't optimize
Tom> the whole thing for speed using threads yet, only for ease of
Tom> programming.

	You can't just use threads "sparingly". You have to integrate
 them into the design of your code. Failing that, the use of threads
 is wildly sub-optimal, and one would probably be better off without
 it. 

	manoj

ps. Look at Using signals with POSIX threads, at
<URL:http://developer.austin.ibm.com/sdp/library/aixpert/aug95/aixpert_aug95_signal.html>,
 from AIXpert Magazine's August 1995 issue

-- 
 "Bureaucracy is a giant mechanism operated by pygmies." Honore de
 Balzac
Manoj Srivastava  <srivasta@acm.org> <http://www.datasync.com/%7Esrivasta/>
Key C7261095 fingerprint = CB D9 F4 12 68 07 E4 05  CC 2D 27 12 1D F5 E8 6E


Reply to: