I can list part of the problems that will show up when writing a moderately-sized concurrent application with shared memory:

  • Locking granularity
  • Choice of synchronization primitive
  • Number of threads
  • Method for decomposing into threads (data parallelism; task parallelism; how threads are organized, etc)

What are other similar problems?

  • 4
    first of all, testing. Typical concurrency bugs I find in my code show off as hard-to-reproduce "impossible" errors, often much later after I wrote the code. I consider myself lucky if my mistake leads to well-reproducible deadlock / data race – gnat Jun 20 '12 at 17:40
  • 1
    Couldn't agree more. Debugging concurrent programs is insanely difficult without a fine tuned sense of the realm. I think its even harder in shared memory systems compared to message passing systems but that is just me probably. – Rig Jun 20 '12 at 17:55

The two most common problems in my experience of taking a class on the subject were the debugging of programs and the efficient distribution of resources.

Debugging a parallel program particularly on an independently managed thread system is astonishingly difficult. It's non-deterministic which means that your program may work 999 times out of a 1000 and that one time it fails just because something arrived in the wrong order or the thread manager didn't allocate correctly.

Atomicity errors are also common, this is why functional languages are becoming popular to handle concurrency as they wall off state (it's not that they don't have a state, it's that it's transparent).

The other big issue is communication. This falls into two categories: Resourcing and Time. Resourcing refers to often limited cache sizes and memory allocations for individual processes. Parallel systems often see use in data heavy applications, thus proper flow and packaging of that data is important. There's also the issue of dealing with the fact that there may not be a good algorithm that efficiently does what you want. Some tasks aren't not easily parallelizable and even if they are, the communication involved may render any speed gains moot. Time refers to the communication times between processors. This is less an issue in "multicore" systems than in distributed ones but it's still a problem.

You might ask why I'm talking about distributed systems at all. Well, some "shared memory" systems are actually distributed with particular facilities that make them function as shared memory.


The last big problem I encountered was working around bottlenecks. I had a lot of fields that were frequently referenced by many threads, and, with the program running flat out, I'd have CPU usage in the 8 to 10 percent range. Hopefully your threads won't interfere with each other like that, but I spent a lot of time pausing my debugger and checking who was stuck where. I ended up breaking up my lock/synchronized blocks to look at a minimum number of fields and working with local variables whenever possible. (There are plenty of other reasons to stick with local variables. This was just one more.)

I also ended up putting blocks of code in separate threads. This might use up a bit of CPU in overhead, but I had 90 to 92 percent available. The code in the separate thread didn't have to wait while it's parent thread waited on a lock and didn't hold up the parent thread while it waited on a lock.

And I went looking for data structures that were both thread-safe and thread-efficient. That's how I found Java's ConcurrentSkipListMap.

Everything else here, as I write this, is more important, and maybe you won't get into quite the tangle of threads I did, but, as I say, this is where I ended up. It's really just a rehash of the problems you listed, but faced after the decisions had supposed all been made and implemented.


All of the above, in addition to the fact that one has to assume an arbitrary amount of time may pass for other threads (remote or otherwise) during and in-between the execution of a single instruction in a given thread.

Unless you can work with assembly, higher level languages (especially things like Java and C#) make this problem difficult to work around without inline assembler, "intrinsics", or a modicum of "atomized" operations exposed as functions.

Even so, there exist "atomic" instructions that fail spuriously on some platforms, and so have to be used in a loop anyway (which is the C++ standard committee's rationale behind compare_exchange_strong and compare_exchange_weak).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.