Why are multi-threading programs more prone to errors?
Overview

Multi-threaded programs are more prone to errors than single-threaded programs because of the problem of concurrency bugs.  Concurrency is hard for most developers to reason about, which causes many bugs in multi-threaded programs; this issue normally doesn't arise in single-threaded programs, because single-threaded programs typically have no concurrency.

Mutable shared state, and plan interference

The core problem is due to the combination of multi-threaded programming plus mutable shared state.  If there was no shared state, or if all shared state was immutable, then you wouldn't have to worry about concurrency bugs.  But when you have mutable shared state, then the programmer has to start thinking about all possible interleavings of the thread A's accesses to the shared state with thread B's accesses to the shared state.  There can be exponentially many possible interleavings that the programmer needs to reason about, if they want to avoid concurrency bugs, and no surprise, that's non-trivial.

Mutable shared state introduces the possibility of data races and other kinds of concurrency bugs.  These can be tricky to reason about.

Multi-threaded programming makes it easy to have plan interference problems.  Programmers tend to think in a single-threaded way: they think about a single sequential path of execution.  When they're writing the code for a particular function/method/module, they build in their head a "plan" of the operations/actions they want to have happen, typically based on a single-threaded mental model (e.g., based on the implicit assumption that nothing else will interrupt them in the middle of this sequence of actions or concurrently modify their internal state).  However, when you have multiple threads with mutable shared state, they can cause unexpected interference while the code is in the middle of execution (changing something out from under the code while it is in the process of executing the programmer's plan).

For example, here is one classic example of plan interference.  Imagine you have a publish-subscribe data structure that lets clients register themselves as observers, and delivers events to all registered observers.  Imagine its code is in the middle of delivering an event to the observers, one by one, and the code of one of the observers happens to mutate the underlying state; this mutation might cause the notifications to the other observers to happen in the wrong order, or to expose an inconsistent state to them.

Further reading

For a more general description of the plan interference problem and more general discussion of why multi-threaded programming is more error-prone, I recommend the following papers:


The Problem with Threads.  Edward A. Lee.  IEEE Computer, May 2006.
Concurrency Among Strangers: Programming in E as Plan Coordination.  Mark S. Miller, E. Dean Tribble, and Jonathan Shapiro.  TGC 2005.  (See especially Sections 1-4.)


Candidate solutions

Multiple candidate solutions have been proposed:


One possible solution is to avoid using threads, and program in a sequential single-threaded way.  This is how Javascript works in browsers, for instance.
Another is to use threads but avoid all mutable shared state.  For example, Erlang.
A third is to use message-passing rather than mutable shared state for interaction between the threads.  One can build systems based on the actor model.  For example, E.
A fourth is to use threads with mutable shared state, but just be really careful.  You can have systematic ways of locking shared data and preventing data races and avoiding plan interference and so on.


See also https://cs.stackexchange.com/a/27803/755 for similar ruminations (I borrowed some text from there).