Live data from Hacker News

Fork() without exec() is dangerous in large programs

evanjones.ca

81–90 of 106 posts

Re: Fork() without exec() is dangerous in large programs

#81
In some programs, fork(2) is the right way to do concurrency. It's simple (API-wise), and it makes IPC explicit, as opposed to the implicit IPC of threads. clone isn't posix, so you can forget it, if you don't feel like being behind systemd in the "screw any system that isn't Linux" line.

In places where you need the speed, threads are useful, but they're harder to use than forks.

Annoyingly, threads and forks don't work well together. zzzcpan already talked about how to fork threaded code. As for the problem of libraries using threads, if a library you're using is using threads, and it's documented (it probably is), and you didn't know about it, I'll pencil that in as your fault.

Other comments in this thread have dismissed fork(2) as a bad job entirely, but I don't think I agree. It's an effective way to do simple multiprocessing, and it's a lot simpler than threads in many contexts.

Re: Fork() without exec() is dangerous in large programs

#82
post #65
post #48

Earlier quoted context omitted.

Then you should read what POSIX says about pthreads, regarding mandatory and optional API support, specially the implementation semantics and UB. For example, what happens to the interactions of signal handlers semantics and thread scheduling. Or what happens to the threads when the process does a fork().

A thread belongs to a process so, for example, whatever the "second" thread does is the same as what a process does. POSIX defines a few mechanisms for IPC. What is optional and what is mandatory in pthreads doesn't concern me as i don't even like pthreads. (will still read what you said, later) CSP stile concurrency over pipes (be they named or not), shared memory and synchronizations over it. POSIX defines mechanis…

> A thread belongs to a process so, for example, whatever the "second" thread does is the same as what a process does.

Wrong, you can fork() and only the thread issuing the fork () call survives the fork(), for example.

There are quite a few other differences.

> POSIX defines mechanisms that work as expected over UNIX platforms.

Not really, there are a few semantic differences and corner cases specially outside the GNU/LINUX, *BSD ones.

Re: Fork() without exec() is dangerous in large programs

#83
post #18

Face the fact, fork() is fundamentally flawed!

I would like to see the reasons this comment was down-voted. It is true that fork(2) is simple and elegant design, but it involves a huge accidental complexity and hidden implications like in the original article. There is a reason that Linux has clone(2) underneath.

[deleted]

Re: Fork() without exec() is dangerous in large programs

#84

Hmm, consider we'd want to keep fork(), then the only safe way to deal with this would be, that critical sections were actually transactions implemented on the OS level and at after fork() in the child all transactions in flight get rolled back before returning from fork(). I see two implementation challenges with that suggestion: 1.) implementing that transaction mechanism as a kernel feature: When entering a CS mar…

Sounds like you are trying to re-invent Software Transactional Memory - was a nice idea 10 years ago, but after lots of research it is still not available in common runtimes... Some more problems to consider: 1. how do you roll back IO that happens in a critical section? 2. your global semaphore will be highly contended and basically destroy scalability

re 1) good point, didn't think of that

re 2) the fork global lock satisfies every requirement for a multiple-readers/single-writer, with fork() being the only writer. Unless the program does a lot of fork()-ing it should hardly ever run into contention. The moment fork() waits on the lock, further attempts on read-lock are delayed until after fork() completes and the existing reads are waited for, before fork()-ing.

Re: Fork() without exec() is dangerous in large programs

#86
post #82
post #65

Earlier quoted context omitted.

A thread belongs to a process so, for example, whatever the "second" thread does is the same as what a process does. POSIX defines a few mechanisms for IPC. What is optional and what is mandatory in pthreads doesn't concern me as i don't even like pthreads. (will still read what you said, later) CSP stile concurrency over pipes (be they named or not), shared memory and synchronizations over it. POSIX defines mechanis…

> A thread belongs to a process so, for example, whatever the "second" thread does is the same as what a process does. Wrong, you can fork() and only the thread issuing the fork () call survives the fork(), for example. There are quite a few other differences. > POSIX defines mechanisms that work as expected over UNIX platforms. Not really, there are a few semantic differences and corner cases specially outside the G…

>Wrong, you can fork() and only the thread issuing the fork () call survives the fork(), for example.

Thinking about it, it makes perfect sense.

Threads are processes that share the same memory space. When a thread is created (via pthread_whatever() or clone()) it starts executing some code that is practically in a $RANDOM place. So how would one go about copying the state of a process that has threads ? Freeze all the threads and copy everything ? That way the thread, that is probably doing some job at the moment, would end up being cloned without it knowing. So you would get two processes (threads are processes) that are doing exactly the same thing. Until something external changes their (internal) state, that is.

Non-determinism should always be avoided if possible.

This still does not say that one couldn't write portable, threaded, UNIX compliant code. Nor that those undefined behaviors from the specs would be fixable by rewriting the spec (by adding atomic fork+exec, that seems to me to be the context of this whole thing).

Re: Fork() without exec() is dangerous in large programs

#87
post #52

Earlier quoted context omitted.

> What exactly is simple and elegant about it? Spawn-style APIs have to take a billion parameters that are mostly set to defaults, for things like working directory, environment variables, user ID, controlling terminal. Whereas fork+exec means you can express these things in a more compositional, buildery style: fork, change the two things you actually need to change, then exec.

I don't think this makes the case for elegance at all. Global vars set wherever in a program are preferable to explicit params when creating a new process? And there's no way to have default params or anything?

> Global vars set wherever in a program are preferable to explicit params when creating a new process?

The outside world is always going to be implicit global mutable state; spawning an arbitrary process to do who-knows-what is inherently that kind of problem. If you could express it as an actual function you wouldn't need to run a separate process.

> And there's no way to have default params or anything?

Well not in C. You can sort-of do it by having a struct with fields that you fill in where all-bits-zero is the default but that's generally more trouble than it's worth.

Re: Fork() without exec() is dangerous in large programs

#88
post #28

Earlier quoted context omitted.

true... ...this applies not just to threading, but other things that are not in the domain of the library such as memory allocation strategy or communication mechanism. Libraries should be small, focused and decoupled. This is the way to proper re-usability.

Like left-pad?

That's a function deployed as a library, not a real library; the word library implies many functions.

Re: Fork() without exec() is dangerous in large programs

#89
post #76

Earlier quoted context omitted.

> If you're spawning threads implicitly then you're no longer a library, you're a framework. That't not what makes a framework. It's all about inversion of control, you call a library and a framework calls you. http://stackoverflow.com/questions/3057526/framework-vs-tool...

If it's spawning threads, it's controlling the control flow. Either it's calling you, or the control flow is out of control.

No. His definition is correct. If you make the call, it's a library, that it might use threads during that call does not make it a framework. Libraries don't mean you're in full control of everything, they only mean you invoke all interactions and get results back rather than you subclass something and plug a part into a framework. If you have to subclass something, it's a framework.

Re: Fork() without exec() is dangerous in large programs

#90

Earlier quoted context omitted.

Fork/Exec is only good if you are going to run a different program then you are currently executing. If you want a series of worker threads that are effectively the same as the master/initial thread. The proper way to do this clone(2) not fork, or fork/exec. When used properly clone allows your group of threads to share a single PID, and TGID (Thread Group IDentifier). This cuts down on kernel resources your process(…

...And why would you want that? The advantage for fork(2) over clone is that you CAN'T share those datastructures, making it harder to write code resulting a deadlock: you have to share explicitely. As for the kernel resources for the process, you'd be surprised how large RAM has gotten these days...

   ...And why would you want that?
All your applications share the same virtual memory space. So you have a much higher hit rate in your TLB, then without. It greatly lowers the chance that shared memory will leave L3 cache.

Cache hit rates are very important...

     The advantage for fork(2) over clone is that you CAN'T
     share those datastructures, making it harder to write
     code resulting a deadlock
Dead locking has nothing to do with how RAM is accessed or how virtual memory is partitioned, it has to do with how you are managing your locking. Modern memory/instruction re-ordering is stupidly fast. If you are locking memory/data structure locations you are likely doing something wrong. Concurrent Memory fences are around 3 orders of magnitude faster then locks.

     As for the kernel resources for the process, you'd be
     surprised how large RAM has gotten these days...
TLB/L2/L3 cache is still premium real-estate. RAM size is awesome! I love waiting 100,000cycles to get the page I requested.
Post reply on HN