1

You can say this is opinion based, but the problem is that everyone's opinion on this issue is same.

Even the lead developers say that creating the threads is easy, dealing with them together is difficult. It isn't a newbie's job.

All I know is that to deal with threads you have to lock all shared data till each thread finishes accessing it on its own (rather than getting interrupted by kernel), and then unlock it.

That will solve all deadlocks and race conditions. Won't it?

What point am I missing here?

5
  • 1
    see also How bad would it be to obtain a lock on every object?
    – gnat
    Dec 14, 2015 at 8:58
  • Sometimes it's nice to leave the question open for 24 hours to make sure more people have a chance to submit a quality answer (not talking about myself here).
    – Den
    Dec 14, 2015 at 9:15
  • 1
    The answers pointed by gnat contain the top #1 reason why this is difficult: non-determinism. We are wired to troubleshoot deterministic problems, and fail miserably if they are not - we can't apply the scientific approach and are back to when we were 7, trying to grasp the world but without the methodological foundations.
    – user44761
    Dec 14, 2015 at 9:53
  • @Den They can post their answers on the original question, assuming the topic isn't already adequately covered there. Dec 14, 2015 at 15:45
  • Ever consider writing a multi-threaded system and see if the "solve all deadlocks and race conditions" is real or just hypothetically correct?
    – JB King
    Dec 14, 2015 at 16:08

4 Answers 4

3

Locking objects can be the cause of deadlocks if you're not careful. Suppose you have two data structures A and B that need to have data in them synchronised. Thread 1 updates A with new information, then updates B to match. Thread 2 updates B with new information, then updates A to match.

So the workflow for thread 1 might be:

 1. Receive new data.
 2. Lock A.
 3. Update A.
 4. Lock B.
 5. Update B to match.
 6. Unlock B.
 7. Unlock A.
 8. Goto 1.

Note that we have A and B locked at the same time so somebody else doesn't modify B until we've got our changes synchronized across the two.

Suppose threads 1 and 2 both receive new information at the same time and need to update the data structures. The order in which things could happen is totally unpredictable, but it could be:

 1. Thread 1 locks A
 2. Thread 1 updates A
 3. Thread 2 locks B
 4. Thread 2 updates B
 5. Thread 1 now needs to update B, so it tries to lock B.  B is already locked so it waits.
 6. Thread 2 now needs to update A, so it tries to lock A.  A is already locked so it waits.
 7. Oops - deadlock.  The whole program is now permanently stuck.
2
  • There is actually a very simple algorithm using instrumented locking calls that will detect if your application ever could lock two mutexes in a different order. Which would be a potential deadlock. But of course there is the rule that random, thoughtless use of mutexes will lead to deadlock.
    – gnasher729
    Dec 14, 2015 at 10:24
  • 1
    In my example, ensuring that the mutexes are locked in the same order by both threads would fix the problem.
    – Simon B
    Dec 14, 2015 at 10:41
13

Concurrent programming is difficult, notably because you need to deal with synchronization issues.

All I know is that to deal with threads you have to lock all shared data till each thread finishes accessing it on its own (rather than getting interrupted by kernel), and then unlock it.

You don't know enough about threads. Read some POSIX thread tutorial. You not only need to use mutexes, but you should avoid deadlocks, and you probably need condition variables.

And in practice, finding good trade-offs is difficult (see the table here to grasp the typical timings of operations). Locking granularity matters a lot (you probably don't want to lock & unlock some mutex every two lines).

At the hardware level, cache coherence is important in multi-core processors, and memory models, memory ordering are difficult to understand. In practice, a multi-core system does not behave "intuitively" w.r.t. time (in other words, each core might have its own notion of time & clock).

4
  • Doesn't that link also talk about mutex locks and condition variable which are used to make the threads wait after their turn is over. Which point is difficult. Point it out. I created this thread to know that.
    – CoffeeDay
    Dec 14, 2015 at 8:39
  • 1
    @CoffeeDay, how many hours have you spent debugging a multi-threaded system on a bug that is tricky to reproduce? In a single threaded system, it can be easier to reproduce an issue consistently. Deadlocks and race conditions happen or else they would just be urban legends by now, no?
    – JB King
    Dec 14, 2015 at 23:40
  • 1
    I think your last point should be your first. Threads are hard to reason about because their behavior is closely tied to the complexities of modern hardware. Grappling with this has become more important as single-core performance hits engineering roadblocks and performance gains increasingly come from parallelism. Dec 15, 2015 at 5:21
  • @JBKing None. That's why I created this thread to know what I need to know. I have started with basic problems like multiple producer-consumers. I want to create difficult multithreaded projects at home in order to learn.
    – CoffeeDay
    Dec 15, 2015 at 6:38
4

As usual, there is a trade-off involved. Doing everything in one thread is simple and risk-free. Doing things in multiple threads increases performance, but introduces risk of consistency errors. Using locks to guard against such errors can negate the entire performance advantage for which you introduced threads in the first place.

In other words, maintainability, performance and correctness form a triad of requirements, of which only two are easy to fulfill simultaneously. There is a reason why so many alternative concurrency models are being floated nowadays; the problem is only going to become more pressing in the future, because CPUs don't get faster anymore, just more parallel, and the average programmer isn't up to making productive use of them with our existing models.

3

lock all shared data till each thread finishes accessing it on its own

This is the difficult bit. You have to correctly identify all places in the software where shared data is accessed. You then have to apply locks at the correct scope level: if you have too much locked, then only one thread will be able to proceed (e.g. Python's global interpreter lock, GIL). If you lock/unlock too frequently, that will slow down the program.

Often you will find some parts of the architecture that are highly contended. Then you end up redesigning large areas of the system to reduce that contention.

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