Programming with concurrency is hard. Concurrency can make programs faster than sequential ones, but having multiple threads read and update shared variables concurrently and synchronize with one another makes programs more complicated than programs where only one thing happens at a time. Why are concurrent programs more complicated than sequential ones? There are, at least, two reasons:
The execution of a sequential program is usually deterministic: If you run the program twice with the same input, the same output will be produced. Bugs are reproducible and thus easy to track down, for example by instrumenting the program. They are called Bohrbugs. However, the output of running concurrent programs depends on how the execution of the various threads are interleaved. Some bugs may occur only occasionally and may never occur when the program is instrumented to find them. We call these Heisenbugs—overhead caused by instrumentation leads to timing changes that can make such bugs less likely to cause havoc.
In a sequential program, each statement and each function can be thought of as happening atomically (indivisibly) because there is no other activity interfering with their execution. Even though a statement or function may be compiled into multiple machine instructions, they are executed back-to-back until completion. Not so with a concurrent program, where other threads may update memory locations while a statement or function is being executed.