Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Open issues in the specification for fork()?

28 views
Skip to first unread message

Markus Elfring

unread,
Nov 23, 2013, 1:05:22 PM11/23/13
to
Hello,

I have read the document
"http://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html" once more.
I find that some details will need further clarification, won't it?

1. Do you interpret the wording
"... If a multi-threaded process calls fork(), ..."
from the section "DESCRIPTION" in the way that it is not specified which
behaviour should be expected for the address space in an ordinary
single-threaded process?

2. Is the wording
"... The fork() function is thus used only to run new programs, and the
effects of calling functions that require certain resources between the call to
fork() and the call to an exec function are undefined. ..."
from the section "RATIONALE" a contradiction to the previous information
"... Consequently, to avoid errors, the child process may only execute
async-signal-safe operations until such time as one of the exec functions is
called. ..."?

3. Is asynchronous-signal-safety also relevant for the single-threaded use case
here?
Do you know interesting software design challenges or "realistic problems"
for this situation?

Regards,
Markus

Måns Rullgård

unread,
Nov 23, 2013, 1:32:54 PM11/23/13
to
Markus Elfring <Markus....@web.de> writes:

> Hello,
>
> I have read the document
> "http://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html"
> once more. I find that some details will need further clarification,
> won't it?
>
> 1. Do you interpret the wording
> "... If a multi-threaded process calls fork(), ..."
> from the section "DESCRIPTION" in the way that it is not specified which
> behaviour should be expected for the address space in an ordinary
> single-threaded process?

No. See the top of the DESCRIPTION section:

The fork() function shall create a new process. The new process (child
process) shall be an exact copy of the calling process (parent process)
except as detailed below.

This adequately defines the single-threaded case. The part you quote
details the behaviour in a multi-threaded process.

> 2. Is the wording
> "... The fork() function is thus used only to run new programs, and
> the effects of calling functions that require certain resources
> between the call to fork() and the call to an exec function are
> undefined. ..."
> from the section "RATIONALE" a contradiction to the previous information
> "... Consequently, to avoid errors, the child process may only execute
> async-signal-safe operations until such time as one of the exec functions is
> called. ..."?

No. The part you quote from the RATIONALE section is an observation of
the typical use of fork() in a multi-threaded program (before the comma),
and a note about the consequences of the restrictions given in the
DESCRIPTION (after the comma).

The RATIONALE section exists only as background information which can be
useful for understanding why a particular behaviour is specified. If it
appears to contradict the DESCRIPTION, the latter wins.

> 3. Is asynchronous-signal-safety also relevant for the single-threaded
> use case here?

No. In the single-threaded case the state of the child process is known
so no limitations apply beyond any already present in the parent. Note
however that ordering of operations on resources shared between the two
processes (e.g. file descriptors) is not defined unless explicitly
synchronised by other means.

--
M�ns Rullg�rd
ma...@mansr.com

Chris Vine

unread,
Nov 23, 2013, 5:13:30 PM11/23/13
to
On Sat, 23 Nov 2013 19:05:22 +0100
Markus Elfring <Markus....@web.de> wrote:
[snip]
> 3. Is asynchronous-signal-safety also relevant for the
> single-threaded use case here?
> Do you know interesting software design challenges or "realistic
> problems" for this situation?

After fork() the child process is single threaded, consisting only of
a continuation of the thread which called fork(); however, it inherits
the state of the whole parent process. In a multi-threaded program, only
the parent remains multi-threaded.

This means that if, for example, a thread other than the forking()
thread holds a lock or other thread resource, it will never be released
in the child process. If the child process subsequently attempts to
lock a lock so held, such as used by malloc(), it will deadlock. This
is the principal reason for the async-signal-safe requirement:
async-signal-safe functions do not use such resources and so will not
misbehave.

This is irrelevant to single threaded programs. They can call whatever
they like after a fork().

Chris

Markus Elfring

unread,
Nov 24, 2013, 12:56:53 AM11/24/13
to
> This is irrelevant to single threaded programs. They can call whatever
> they like after a fork().

Unless the processing context will be switched to the implementation of a signal
handler. (Asynchronous-signal-safety should be considered in an other situation
then, shouldn't it?) ;-)

Regards,
Markus

Casper H.S. Dik

unread,
Nov 24, 2013, 5:27:35 AM11/24/13
to
Markus Elfring <Markus....@web.de> writes:

>Hello,

>I have read the document
>"http://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html" once more.
>I find that some details will need further clarification, won't it?

>1. Do you interpret the wording
> "... If a multi-threaded process calls fork(), ..."
> from the section "DESCRIPTION" in the way that it is not specified which
>behaviour should be expected for the address space in an ordinary
>single-threaded process?

Well, the description starts with:

" .... The new process (child process) shall be an exact copy of
the calling process (parent process) except as detailed below:"

So what is listed for the multi-threaded process is one of the
exceptions; specifically, I believe it says that the stacks of the other
threads aren't part of the new process.

>2. Is the wording
> "... The fork() function is thus used only to run new programs, and the
>effects of calling functions that require certain resources between the call to
>fork() and the call to an exec function are undefined. ..."
> from the section "RATIONALE" a contradiction to the previous information
> "... Consequently, to avoid errors, the child process may only execute
>async-signal-safe operations until such time as one of the exec functions is
>called. ..."?

This is all about multi-threaded programs; and in such programs what functions
you can call in the child after fork() is restricted to async-signal-safe
functions.

>3. Is asynchronous-signal-safety also relevant for the single-threaded use case
>here?

No, except when fork() is called from a signal handler.

> Do you know interesting software design challenges or "realistic problems"
>for this situation?

Yes. Any form of malloc(), exit() instead of _exit(), etc, may lead
to problems which likely happen randomly and probably after you've
shipped the code to a customer.

Casper

Rainer Weikusat

unread,
Nov 24, 2013, 10:39:31 AM11/24/13
to
Casper H.S. Dik <Caspe...@OrSPaMcle.COM> writes:
> Markus Elfring <Markus....@web.de> writes:

[...]

>>2. Is the wording
>> "... The fork() function is thus used only to run new programs, and the
>>effects of calling functions that require certain resources between the call to
>>fork() and the call to an exec function are undefined. ..."
>> from the section "RATIONALE" a contradiction to the previous information
>> "... Consequently, to avoid errors, the child process may only execute
>>async-signal-safe operations until such time as one of the exec functions is
>>called. ..."?
>
> This is all about multi-threaded programs; and in such programs what functions
> you can call in the child after fork() is restricted to async-signal-safe
> functions.

This is all about the people who wrote the pthreads-specification
fighting the other tribe of people who are Violently(!!1) opposed to
multi-threading but purposely not defining any sensible semantics for
fork in multi-threaded processes. Consequently, the UNIX(*) standard is
useless here and best ignored: For any existing platform, the behaviour
is necessarily defined and code can be written to work in such an
environment.

[...]

>> Do you know interesting software design challenges or "realistic problems"
>>for this situation?
>
> Yes. Any form of malloc(), exit() instead of _exit(), etc, may lead
> to problems which likely happen randomly and probably after you've
> shipped the code to a customer.

If the failures are demonstrably random, that is, they occur because the
people who wrote the system code added stuff like

if (random() % 15 < 3) *(int *)(random()) = 12;

with some guard to trigger only in multi-threaded programs, that would
be a problem of the customer who should be more careful when chosing
suppliers.

Casper H.S. Dik

unread,
Nov 24, 2013, 7:23:05 PM11/24/13
to
Rainer Weikusat <rwei...@mobileactivedefense.com> writes:

>This is all about the people who wrote the pthreads-specification
>fighting the other tribe of people who are Violently(!!1) opposed to
>multi-threading but purposely not defining any sensible semantics for
>fork in multi-threaded processes. Consequently, the UNIX(*) standard is
>useless here and best ignored: For any existing platform, the behaviour
>is necessarily defined and code can be written to work in such an
>environment.

I think this is completely unfair as it are those who worked on the
implementation and those who wrote the standard knew exactly the
two choices they had; they picked one of the two choices and you
seem to disagree with that particular choice.

>If the failures are demonstrably random, that is, they occur because the
>people who wrote the system code added stuff like

You don't quite get exactly what I'm saying; any program using threads
no longer has a well-defined order in which all the instructions are
evaluated. In such a problem errors in logic, such as those not
adhering with "calling only calling async-signal-safe after fork",
will occur seemingly randomly as it depends on what the other threads
are doing.

Casper

Chris Vine

unread,
Nov 24, 2013, 8:19:03 PM11/24/13
to
On 25 Nov 2013 00:23:05 GMT
Casper H.S. Dik <Caspe...@OrSPaMcle.COM> wrote:
[snip]
> I think this is completely unfair as it are those who worked on the
> implementation and those who wrote the standard knew exactly the
> two choices they had; they picked one of the two choices and you
> seem to disagree with that particular choice.

It was also in my view the better choice - I just don't like forkall(),
as it can make the program more difficult to reason about, particularly
for programs using a shared resource such as I/O (and what don't),
which can turn forkall() into a bug factory.

The only purpose for calling POSIX fork() in a multi-threaded program is
to follow it with a call to exec*() (perhaps after setting up pipes with
dup2() and the like), in which case the async-signal-safe restriction
is fine: you do your other set up (including any memory allocation)
before the fork() and not after. For other purposes, in a
multi-threaded program conforming to POSIX you start a thread, not a
process.

pthread_atfork() is completely broken. That can cause problems with
single threaded programs that use multi-threaded libraries without
taking the care to understand that what they are linking to is
multi-threaded, but there we are. Know what you are linking to is, I
guess, the best answer.

Chris

Casper H.S. Dik

unread,
Nov 25, 2013, 7:08:24 AM11/25/13
to
Chris Vine <chris@cvine--nospam--.freeserve.co.uk> writes:

>It was also in my view the better choice - I just don't like forkall(),
>as it can make the program more difficult to reason about, particularly
>for programs using a shared resource such as I/O (and what don't),
>which can turn forkall() into a bug factory.

Originally, Solaris did came with its own threads library and in that
thread implemetation had fork1() and fork() (aka forkall()).
Later they added a pthread implementation and the pthread implementaton
needed to use fork1() for fork(), so there were two different
libraries.

If you didn't link with -lthread or -lpthread, you got stubs for many
of the thread calls (such as mutex primitives) so a library could be
written as if it was multi-threaded but would work in a single threaded
application.

>The only purpose for calling POSIX fork() in a multi-threaded program is
>to follow it with a call to exec*() (perhaps after setting up pipes with
>dup2() and the like), in which case the async-signal-safe restriction
>is fine: you do your other set up (including any memory allocation)
>before the fork() and not after. For other purposes, in a
>multi-threaded program conforming to POSIX you start a thread, not a
>process.

And so we now have posix_spawn() which does much of what you generally
do between fork() and exec().

>pthread_atfork() is completely broken. That can cause problems with
>single threaded programs that use multi-threaded libraries without
>taking the care to understand that what they are linking to is
>multi-threaded, but there we are. Know what you are linking to is, I
>guess, the best answer.

That is perhaps more a question quality of implementation and not so
much an issue with pthread_atfork(). In Solaris 10 there is no longer
a difference between a single threaded program and a multi-threaded
program with only one thread. So there is really no issue with linking
with libraries which may use threads.

As a result of making threads the standard, fork() is
now *always* fork1() though forkall() is still available.

Casper

Rainer Weikusat

unread,
Nov 25, 2013, 7:21:38 AM11/25/13
to
Chris Vine <chris@cvine--nospam--.freeserve.co.uk> writes:
> On 25 Nov 2013 00:23:05 GMT
> Casper H.S. Dik <Caspe...@OrSPaMcle.COM> wrote:
> [snip]
>> I think this is completely unfair as it are those who worked on the
>> implementation and those who wrote the standard knew exactly the
>> two choices they had; they picked one of the two choices and you
>> seem to disagree with that particular choice.

[...]

> The only purpose for calling POSIX fork() in a multi-threaded program is
> to follow it with a call to exec*() (perhaps after setting up pipes with
> dup2() and the like),

For some people, the only purpose of fork they can possibly imagine is
as halfway useless incumbment on the way to executing another program
and - suprisingly and certainly by mere coincidence - the people who
wrote the phtreads specification and produced this litte gem are among
them. This makes two extreme standpoints, namely, "multi-threading is
useless and evil and 'cooperating sequential processes' is all God ever
wanted men to use" and "multiple processes running the same program are
totally useless, dangerous, outlandish, alien and socialist(!!1) and our
nice, shiny threads are a much better modern solution to any problem one
could conceivably need to solve" and neither of both is particularly
helpful in the real world (as extreme standpoints are wont to be),
especially considering all the moss which has been growing on this
controversy since it originated last century.

When a process forks, the contents of the memory in the address space of
the new process will be identical to that of the parent at the time of
the fork. Because only the forking thread is duplicated the child, the
effect on any other threads is that they stopped asynchronously at some
unpredictable (as seen from the 'sequential flow of time' of each
particular thread) time between executeing two instructions. This is
exactly what also happens when a signal handler is invoked because of an
asynchronous signal, hence, without special precautions, only
async-signal-safe functions can be called safely in the new
process. Another problem would be that POSIX demands that mutexes can
only be unlocked by the thread which locked them which implies that a
mutex which was held by some other thread than the one which forked
cannot ever be unlocked in the new process.

But signal handlers are not really restriced to async-signal safe calls
because they're free to do anything provided that 'main thread of
execution' was interrupted in an 'async-signal safe section' of the
code (the requirement is [paraphrase] 'all functions defined by this
specification shall work as described in the presence of asynchronous
signal except when function which is not async-signal safe is executed
from a signal handler which interrupted another not async-signal safe
function. In this case, the behaviour is undefined'). Likewise, there's
no restriction regarding what the forked process may safely do provided
all threads were executing something async-signal safe at the time of
the fork and no mutexes held by any of them will be touched in the new
process. And this is perfectly doable, especially considering that -
practically - some things are async-signal safe despite they're not
required to be, eg, blocking on a locked mutex (or semaphore). In an
ideal universe, implementations would be required to document how they
deal with multi-threaded access to shared resources and one could even
envision an API for safe forking modelled similar to the existing
facilities for dealing with asynchronous signals. In absence of both, it
is still possible to confine all other threads of a process to some safe
location during the fork and release them afterwards.

This is obviously not going to work for the most general imaginable
case, where 'the process' runs arbitrary, binary only third-party code
whose actions can neither be controlled nor predicted but this is not
the only case.

Rainer Weikusat

unread,
Nov 25, 2013, 10:52:46 AM11/25/13
to
Casper H.S. Dik <Caspe...@OrSPaMcle.COM> writes:
> Rainer Weikusat <rwei...@mobileactivedefense.com> writes:
>
>>This is all about the people who wrote the pthreads-specification
>>fighting the other tribe of people who are Violently(!!1) opposed to
>>multi-threading but purposely not defining any sensible semantics for
>>fork in multi-threaded processes. Consequently, the UNIX(*) standard is
>>useless here and best ignored: For any existing platform, the behaviour
>>is necessarily defined and code can be written to work in such an
>>environment.
>
> I think this is completely unfair as it are those who worked on the
> implementation and those who wrote the standard knew exactly the
> two choices they had; they picked one of the two choices and you
> seem to disagree with that particular choice.

I 'disagree' with this here:

There are two reasons why POSIX programmers call fork(). One
reason is to create a new thread of control within the same
program (which was originally only possible in POSIX by creating
a new process);

[...]

When a programmer is writing a multi-threaded program, the first
described use of fork(), creating new threads in the same
program, is provided by the pthread_create() function. The
fork() function is thus used only to run new programs,

because while this is not an outright lie, it's a carefully
weasel-worded piece of disinformation supposed to lent some appearance
of rationality to the entirely political bias of the author: Fork does
not create 'a new thread of control running the same program' (which has
a process chained to its ankle for some shitty, historical reason nobody
in his right mind could possibly understand) but it creates a new
process running the same program and this includes creating a new thread
of control among some other things, most prominently, a new address
space.

There's also the practical concern that adding a new thread to some
existing code unleashes 'immediate pthread madness' which is supposed to
change the semantics of the working code retroactively: Something which
used to be a perfectly legitimate use of fork is now considered to have
undefined behaviour, regardless of how the interactions between the
existing 'single-threaded process code' and the code running in the new
thread actually happen to work. This means 'using pthreads' is meant to
be an all-or-nothing choice: Either the process remains strictly
single-threaded. Or everything has to be rewritten to be 'strictly
pthreaded' insofar this is possible (which might not be the case since
the code could rely on properties of fork pthread_create does not have)
and this looks a lot like a useless effort to me, IOW, 'pthreads' trying
to punish me for using 'traditionally written UNIX(*) code'.

Casper H.S. Dik

unread,
Nov 25, 2013, 11:55:26 AM11/25/13
to
Rainer Weikusat <rwei...@mobileactivedefense.com> writes:


>I 'disagree' with this here:

> There are two reasons why POSIX programmers call fork(). One
> reason is to create a new thread of control within the same
> program (which was originally only possible in POSIX by creating
> a new process);

The changes for the threads didn't change how fork() worked or
didn't work.

Even before threads were common, fork() had many, many issues, such
as fork()ing the whole status of the stdio library; atexit() handlers.

fork() as a second thread in the same program was always full of
risk.

You almost sound like someone who laments the loss of Linux' clone
model to the pthread model.

It is true, I think, that starting threads from a library is
frowned upon, except if these threads are extremely well-behaved
(and that might include not calling malloc() after control has been
back to the main thread)

Casper

Rainer Weikusat

unread,
Nov 25, 2013, 12:36:27 PM11/25/13
to
Casper H.S. Dik <Caspe...@OrSPaMcle.COM> writes:
> Rainer Weikusat <rwei...@mobileactivedefense.com> writes:
>
>
>>I 'disagree' with this here:
>
>> There are two reasons why POSIX programmers call fork(). One
>> reason is to create a new thread of control within the same
>> program (which was originally only possible in POSIX by creating
>> a new process);
>
> The changes for the threads didn't change how fork() worked or
> didn't work.
>
> Even before threads were common, fork() had many, many issues, such
> as fork()ing the whole status of the stdio library; atexit() handlers.
>
> fork() as a second thread in the same program was always full of
> risk.
>
> You almost sound like someone who laments the loss of Linux' clone
> model to the pthread model.

I wrote something specific about the whole 'semantic unit of text' the
first paragraph of which you quoted above and about another problem with
the pthreads 'make no prisoners' approach. I don't see how the contents
of your text would relate to that. I'm also not aware of any 'losses'
affecting the Linux clone system call which were caused by adding the
functionality necessary for supporting the pthreads signal handling
model (which, while sensible in itself, is another 'disaster as
designed' because it runs counter the intuition of a lot of people, who
presumably use dedicate signal handling threads, thus, limiting the
scalability of their code, despite there's no real reason for that
except that they don't understand the purpose of the pthreads signal
handling approach and consider it "scary and dangerous" because of
that).

While I don't consider stdio particularly useful, I wouldn't go so far
to label it as 'risky on UNIX(*)' and the same is true for 'atexit
handlers'. Using binary-only code with unknown behaviour is 'risky' but
even these dangers can be dealt with.

Drazen Kacar

unread,
Nov 25, 2013, 1:46:36 PM11/25/13
to
Casper H.S Dik wrote:

> If you didn't link with -lthread or -lpthread, you got stubs for many
> of the thread calls (such as mutex primitives) so a library could be
> written as if it was multi-threaded but would work in a single threaded
> application.

Not really. Things worked that way and it was generally known that stubs
in libc exist[1], but that was neither documented nor supported. So the
only entity who could safely write libraries that way was Sun.

I don't know if anyone else wrote them that way. I decided not to, because
the method was not supported and thus likely to cause trouble if the
implementation changes.

[1] And, of course, there were people who wrote multi-threaded program,
but forgot to add one of the thread libraries to the link line. Then the
program would link fine (because of stubs in libc), but none of the
thread functions would work. That was a lot of fun for everyone. :-)

--
.-. .-. Yes, I am an agent of Satan, but my duties are largely
(_ \ / _) ceremonial.
|
| da...@fly.srk.fer.hr

Casper H.S. Dik

unread,
Nov 25, 2013, 2:55:16 PM11/25/13
to
Drazen Kacar <da...@fly.srk.fer.hr> writes:

>Not really. Things worked that way and it was generally known that stubs
>in libc exist[1], but that was neither documented nor supported. So the
>only entity who could safely write libraries that way was Sun.

And they didn't all work either; e.g., pthread_once() didn't work.

>I don't know if anyone else wrote them that way. I decided not to, because
>the method was not supported and thus likely to cause trouble if the
>implementation changes.

>[1] And, of course, there were people who wrote multi-threaded program,
>but forgot to add one of the thread libraries to the link line. Then the
>program would link fine (because of stubs in libc), but none of the
>thread functions would work. That was a lot of fun for everyone. :-)

Yes; fortunately, that was fixed when Solaris 10 was released (in 2005)

But that couldn't be done until we had created a multi-thread library
which behaved more sanely in the presence of just the one thread;
such a library was first shipped with Solaris 8 and was made the default
in Solaris 9.


Casper

Rainer Weikusat

unread,
Nov 25, 2013, 5:16:07 PM11/25/13
to
Casper H.S. Dik <Caspe...@OrSPaMcle.COM> writes:
> Drazen Kacar <da...@fly.srk.fer.hr> writes:

[...]

>>[1] And, of course, there were people who wrote multi-threaded program,
>>but forgot to add one of the thread libraries to the link line. Then the
>>program would link fine (because of stubs in libc), but none of the
>>thread functions would work. That was a lot of fun for everyone. :-)
>
> Yes; fortunately, that was fixed when Solaris 10 was released (in 2005)
>
> But that couldn't be done until we had created a multi-thread library
> which behaved more sanely in the presence of just the one thread;
> such a library was first shipped with Solaris 8 and was made the default
> in Solaris 9.

This is described in some detail in a whitepaper named 'Multithreading
in the Solaris Operating Environment' published around the time of
Solaris 9 which is still worth a read (I actually just reread it). The
main change in order to get a 'sanely behaving threading library' was
switching from M:N threading to 1:1 threading and thus getting rid of
the necessity to use hidden userspace threads in order to try to solve
problems inherent in the M:N approach. This includes the remarkable feat
of accepting that threads are useful because they enable applications to
use multiprocessors AND NOT because they enable university eggheads from
'research assistant' upwards to avoid dealing with state machines in
their code (that's a gross oversimplification, mainly because I really
don't know how to describe this in a more accurate way).

Commercial operating system tend to oscillate between these two models
(with the occasional idiot reimplementing cooperative userspace
threading for the upteempth time), cf the following statement from HP on
'MxN threading in HP-UX 11i':

MxN threads were implemented in response to the perception that
enterprise applications creating large numbers of threads
perform better with MxN threads than with 1x1.

However,

The only real way to know how using the MxN model affects the
performance of your particular application is to benchmark the
two models and compare the results.

Or, to put this into other words "While there's a strong perception that
such applications ought to exist, there's little real evidence that they
actually do".

Casper H.S. Dik

unread,
Nov 26, 2013, 3:33:37 AM11/26/13
to
Rainer Weikusat <rwei...@mobileactivedefense.com> writes:

>Commercial operating system tend to oscillate between these two models
>(with the occasional idiot reimplementing cooperative userspace
>threading for the upteempth time), cf the following statement from HP on
>'MxN threading in HP-UX 11i':

> MxN threads were implemented in response to the perception that
> enterprise applications creating large numbers of threads
> perform better with MxN threads than with 1x1.

>However,

> The only real way to know how using the MxN model affects the
> performance of your particular application is to benchmark the
> two models and compare the results.

>Or, to put this into other words "While there's a strong perception that
>such applications ought to exist, there's little real evidence that they
>actually do".

The argument for "M:N is better" was very strong because, theoretically,
it could be faster and synchronization wouldn't need to use the kernel.

A "1:1" implementation, in practice, however, uncontended locks don't need a
trip to the kernel either and the enormous amount of extra work needed in
the MxN implementation (handling SIGWAITING, a second implementation of
context switching, which actually often did require kernel involvement)

It should also be noticed that when a comparison was done, it was
often using the same libthread library but run so that M == N; but
that wasn't a proper comparison as in that case you still pay for
the penalty for the M:N support.

Casper

Rainer Weikusat

unread,
Nov 26, 2013, 10:46:33 AM11/26/13
to
Casper H.S. Dik <Caspe...@OrSPaMcle.COM> writes:
> Rainer Weikusat <rwei...@mobileactivedefense.com> writes:

[M:N/ 1:1 threading]

> The argument for "M:N is better" was very strong because, theoretically,
> it could be faster and synchronization wouldn't need to use the kernel.
>
> A "1:1" implementation, in practice, however, uncontended locks don't need a
> trip to the kernel either and the enormous amount of extra work needed in
> the MxN implementation (handling SIGWAITING, a second implementation of
> context switching, which actually often did require kernel involvement)

Some people think that 'threads are a way to structure programs', that
is, they're primarily supposed to be helpful for developers. The idea is
typically that 'a thread of execution' can be dedicated to a single task
which progresses intermittently and concurrently with a lot of other
tasks of the same kind: In a single-threaded process, this requires
storing the state of each task in some 'globally accessible' data
structure and continuing to work on some other tasks based on the
recorded state of that whenever the 'current task' can't make progress
anymore. If each task had a dedicated thread, this wouldn't
be necessary because 'the scheduler' would transparently suspend one
thread and switch to another whenever things would otherwise come to a
standstill, thus providing the illusion that each task really runs on a
dedicated computer without having to consider the needs of other tasks.

This, of course, requires creating as many threads as there are
concurrent tasks and for something like a network server, this could
easily mean thousands or even tenthousands of them. Consequently, each
thread needs to be as cheap as possible, in particular, it shouldn't
have any kernel resources like 'a kernel stack' permanently allocated to
it. Also, in order to make programming easy, actual concurrent
execution of threads should rather be avoided: For as long as a thread
is busy, it just keeps running, and once it invokes 'a blocking call' of
some kind, another thread can start to run. In order to avoid starving
tasks, threads are supposed to be 'nice to each other', that is,
explictly yield the processer every now and then when being busy for a
long time. This means cooperative userspace threading. Because there's
no concurrency, synchronization isn't really needed and because there's
no preemption, no complicated userspace scheduler is needed, either. At
least in theory. In practice, once the process blocks in the kernel, all
threads stop running and hence, each and every posssible blocking system
call must be wrapped in some library routine which accomplishes the same
end in some non-blocking way and runs other threads in the meantime. The
net effect of this is usually a lot of borderline insane library hackery
and some rather bizarre restrictions as to what a thread can do or must
not do (the GNU pth paper provides a nice example of that).

Some other people think that threads are supposed to be useful for users
in the sense that they enable these to utilize processing resources such
as multiple processors and/or multiple cores which might be available to
them. This implies that 'a thread' runs code which looks pretty much
like that of a single-threaded in processes wrt to
'multi-tasking'. In order to keep scheduling overhead down, there will
be relatively few threads (somewhat more than available 'processing
things') but they're both supposed to execute in parallell and to work
with 'shared global resources'. This means synchronization is needed and
the kernel scheduler has to deal with them because the kernel is the
privileged piece of coding managing the actual hardware and thus, the
only software capable of doing things like assigned threads to available
processors for execution. Further, this is supposed to work in an
environment shared with other application which also seek to use the
available resources and which don't have been written to cooperate,
hence, preemption is necessary. The most natural choice for this is 1:1
threading where each 'application thread' corresponds with a 'kernel
scheduled entity' of some sort.

Trying to satisfy both groups of people leads to M:N threading which
really just combines the disadvantages of both models without providing
the advantages of either. It ends up as expensive way to screw everyone
alike but only in practice, not in theory.

I was planning to illustrate this using the pre-Solaris 8/9 'threading
workarounds' as example but this text is too long already ...

Kaz Kylheku

unread,
Jan 25, 2014, 10:45:05 PM1/25/14
to
["Followup-To:" header set to comp.unix.programmer.]
The issue is that when a copy is made of a multithreaded process by fork,
it's as if the child is in a parallel universe where all the threads except
for the forking one have suddenly disappeared dead in their tracks.

So for instance, what if one of the threads in the parent was in the middle of
calling malloc, and some pointers in the malloc data structure are in a bad
state? The child inherits them that way.

You can write MT programs that handle fork and keep going.

In the global initialization routine of each module, have it register fork
handlers with pthread_atfork. Have every module do some correct thing when a
fork is going on. The pre-fork handler can ensure that threads are are
"parked" in some quiescent state, where they are known not to be updating
anything that will result in a trashed copy. The post-fork handler in the
parent can then clear the emergency so that things continue chugging along.
The post-fork handler in the child can do something similar, keeping in
mind that there are no threads there.

It's a lot of responsibility, possibly complex coding and ... good luck
getting third-party components to cooperate.
0 new messages