Hacker News new | comments | show | ask | jobs | submit login
Why Threads Are A Bad Idea (for most purposes) [pdf] (pacbell.net)
22 points by nickb on Dec 16, 2008 | hide | past | web | favorite | 24 comments



Generally speaking, I would agree that if there's any alternative at all, you shouldn't use threads. But when you do need them, you really need them. And I don't think they are nearly as difficult to program as most people make them out to be.

My guess is that people get scared off because the failure mode is so daunting. It's easy to write a threaded app that works fine for simple tests, then fails miserably when used under load. When that happens, it's nearly impossible to isolate a good test case for debugging. That's enough to scare off most people.

Another reason threads haven't become more popular is because operating system support is generally poor. It's rare for API documentation to say anything at all about multi-threading. There are common-sense rules for adding threads to Mac programs, but I discovered them more or less by trial an error, rather than anything I read in Apple's documentation.

BeOS not only had good support for threads, their use was in fact required in all GUI programs. Every single window runs in its own thread. That forces you to get good at multi-threading in a hurry.

The biggest mistake people make is creating too many locks. The most common failure mode is where thread A is holding lock 1 and is trying to acquire lock 2, while thread B is holding lock 2 and trying to acquire lock 1. A former boss (who worked at Be, as a matter of fact) suggested you can solve this one by always acquiring the locks in the same order everywhere. I say a better method is to never hold more than one lock at a time.

The best way to do that is share pretty much nothing between threads. Write each thread as if it were a separate program, as much as possible. Have the thread get new work out of a queue, and place finished work into a different queue. That way, you're guaranteed to never hold more than one lock at a time.

Successful multi-threaded programming requires a lot more discipline than normal. Failure to follow this rule means you'll be getting those impossible-to-debug lockups.


Another reason threads haven't become more popular is because operating system support is generally poor.

I'd say that on Windows threads are first-class citizens and are very actively used. Windows even comes with a ready-to-use thread pooling and ability to run arbitrary code in different thread contexts. Moreover, Windows-specific documentation often has a special paragraph regarding thread safety for every clib/stl function.

When I moved to Linux world I indeed found that threads aren't treated well. In fact I still don't have a full and deep understanding of threading outside of Win32, I suspect because different UNIXes have them differently. Perhaps it's the lack of POSIX standardization what makes them scary.

I've built a few multi-threaded windows server-like services and never found them to be particularly difficult to grasp or debug: system APIs, docs and tools were excellent.

To summarize, only after becoming a full time Linux/OSX programmer I finally started to see why Internet is full of "threads are evil" articles.


I think part of the reason is that Linux and Unix processes are less expensive to start, they are more memory efficient because shared libraries are loaded into memory only once (not once per process) and fork is copy on write.

I may be wrong about Windows. I recently tried a Tcl script that launched another Tcl script repeatedly and found additional memory consumption was about 100kb per process. It wold probably have been less if I used fork. It would be interesting to know how Windows compares.


Correction: shared libraries (DLLs) on Windows are shared between processes exactly the same way as it works on Linux, they're called 'shared' for a reason :-) They get "mapped" into virtual address space of every process, but physically there's only one copy, moreover, if you're running as an Admin, a process can alter the code in a shared DLL, causing it to work differently in all other processes that use it, that's one of the tricks that infamous rootkits do to hide themselves.

You are right, Windows processes are heavy. Moreover, context switch between processes on Windows is also a little slower, because NT-based kernels went "maximum security" route and perform full process isolation, that's also why Windows doesn't have a fork-like syscall, only environment variables, IIRC, can be cloned. (which is how, I suspect, Linux threads are implemented - they're probably share-everything fork calls).

On the other hand thread/fiber switch on Windows, I believe, is fastest of all popular x86 OSes.

All in all I like Windows process model a lot more. Clear separation between processes (share nothing) and threads (share everything except the stack) is well-known and well documented, and leads to a much nicer server-like application programming experience.

Although... I really like how easily you can nuke frozen processes on Linux if there is a problem. No programming tools/knowledge required. Dealing with hung threads would have been a lot harder.


Traditionally processes were very expensive to start on windows, threads very cheap. On linux its much of a muchness (or was, probably still is).

Windows would be less then that per thread, probably more per process. But I am thinking back to 2003-ish data ! (really should try some experiments again).


Linux threads are just processes that share their whole address space read-write. There's no special thread primitive.


Deadlock isn't so bad - at least it is obvious when it occurs. The real problems in threading happen when you have race conditions that you can't debug because you can't replicate. In languages like C++ this can give you inexplicable seg faults in code far from the bug that don't even give you a place to start looking.


In summary: They're a bad idea because they're extremely hard to get right. You'll almost certainly create a program that works at most 99% of the time, and you'll never figure out what happened to the other 1% (my words, not Ousterhout's).

He suggests events as a simpler alternative. I would add: Unless you use a language with convenient concurrency semantics like Clojure.


A view from the time machine. That is the 1995 view of threads. Much of it is still true, some of it isn't.

I would recommend that anyone using threads run their program through the Helgrind tool from the Valgrind folk. It does a nice job of noticing if you missed a lock on a shared variable by instrumenting and analyzing your program at run time. (It caught me on one in my most recent program. I knew I hadn't started any threads when I made the access, still I should have had a lock there in case I later changed the initialization order.)


Ironically, Tcl actually does threads in a fairly nice way:

Each thread gets its own Tcl interpreter, and you pass messages back and forth between them. It's a much better approach than the "one big lock". I believe Perl takes a similar approach to Tcl.


I recently wrote a bit of code which throws off a monitoring thread before launching an external process. If the process crashes, the monitoring thread kills the Windows dialog saying it crashed, and continues as normal.


Using threads:

1. Tell the O/S to decide which order all your tasks execute (Wow great I don't need to worry about all that).

2. Oops, now we don't know what order tasks will execute, so we have to program a whole heap of synchronization code.

OR

Do it yourself:

1. Program everything, so you know which order things occur, and don't need to worry about synchronization.


It's ESR's "Threads: Threat or Menace" as a powerpoint...

http://catb.org/~esr/writings/taoup/html/ch07s03.html

The conclusion is still accurate enough though, IMHO.


What about multiple processes? Threads and multiple processes are not the same, and using multiple processes cuts out a lot of the issues with threads while maintaining the concurrency.


http://www.kegel.com/c10k.html gives insights to using threads in server-side software.


"September 28, 1995" If only they found some way to put multiple cores on a single chip, so programs could truly exploit parallelism...


this is total nonsense. Maybe for the php crowd that know only garbage collected 'trivial' languages, but for anyone that has a CS degree, threads are not difficult at all. Imagine trying to write a SQL Database without threads, or many many other applications. Locking isn't all that difficult if you think it through - that used to be called design, but today, that just means what color is the drop shadow box :)

Locking order is important to prevent the classic deadlock, but there is nothing inherently 'too hard' for programmers in using threads.


> CS degree

Dr. Ousterhout has one of those:

http://en.wikipedia.org/wiki/John_Ousterhout

Actually it says he was a professor at UC Berkeley, where he wrote Tcl, Tk, and an OS, amongst other things.

After that, he went on to work at Sun, and then founded his own successful company.


"He received his Bachelor's degree in Physics from Yale University in 1975, and his Ph.D. in computer science from Carnegie Mellon University in 1980."


PostgreSQL is not threaded.


Neither are X servers that handle thousands of events for dozens of windows per second.


right, but the postmaster, supervisory process manages the common work area such that each process is acting as a thread, it just predates threads in its architecture. The rocess per-user model is much like Apache 1.x, which has its drawbacks, but does work well and has for years.


Apache 2.x can be configured to use the prefork MPM which behaves in a similar fashion to 1.x. There are even advantages to this method.

Postfix is another great example of a high quality piece of software that is not threaded. There are many other examples.

It is entirely possible to write complex systems that perform well without threads.


Writing threaded code is not all that difficult. It's finding the bugs that is. It's because the problems are not easily reproducible. The synchronization model in Java makes it all too easy to muck it up. Some simple ideas:

Threads are very handy if they don't talk to each other like java servlets.

If you have a shared variable, make it a monitor.

If you have a share resource resource, make it immutable.

If you have 45 shared variables, you are doing it wrong.

Use library objects like queues to communicate between threads.

Use a shared-nothing model like in erlang.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: