Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

If you think that the Barber problem looks simple, odds are you haven’t tried to implement it. There are quite a few things which can go wrong when we have several agents working on shared data, primarily

Deadlocks Race conditions

A bit off-topic, but I see statements like this all the time, and I always wonder: why is it assumed that you're going to throw together an aggressive design that shoots for theoretically optimally performance, and then trying to ferret out the bugs? Ideally, the solution will be both correct and optimally performant, but if you're worried that you aren't going to get it right, you should start thinking about the best way to fail. It seems wiser to start with a simple dumb solution you know is correct and work carefully towards a more complex, better-performing solution. In that case you would list "crappy performance" as the primary thing that can go wrong in a concurrent system -- but nobody ever does. They're worried about bugs. That means that when they're done with their first attempt, they'll be confident in its performance but not in its correctness.

Here's why I think it's better to start with a simple dumb solution and work from there: you can stop short of an ideal solution. Maybe you only have to optimize it well enough so that it isn't the scaling bottleneck. Or maybe you can just deploy it and tolerate the performance limitations. If you start with code that has the optimal performance characterists but isn't correct, you can't deploy it until you fix all the bugs. You've committed yourself to creating an ideal solution, even though an ideal solution might not be necessary.



The chief problem with this is that most programmers' intuitions are calibrated for single threaded processes; when they think of "what could happen" race conditions and deadlocks aren't there. This, I think, is because they are usually emergent global properties, not localized errors, and we're not used to looking for those or, frankly, especially good at them.

There's a very simple way to prevent all deadlocks and race conditions. It's called in some circles the "Global Interpreter Lock" which converts the system from concurrent to single threaded, and is usually the wrong way to deal with the problem. These locks become performance hurdles over time, and people try to replace them with finer grained locks, and what happens every time is that mysterious and weird concurrency errors happen, sometimes for years, until things finally get ironed out. Happens in Python, Linux kernel, and probably a million other places I'm not immediately familiar with.


Personally, I think it's a problem of trying to cut corners. If you accept the full mathematical complexity of your design, you'll be forced to create a tractable design. Many people create designs that would be intractably complex if they bothered to try to understand them, but they don't try to understand them. They just hope. They'd rather spend 10x time debugging and making random adjustments to make errors less likely than spend x time creating a design that actually works.


Yes, most programming is a matter of solving a sequential design problem - what comes next in process of building this structure? Threaded programming are more like solving a logic or algebra problem - given certain initial conditions, what could or could not be the case?

I'm not sure whether we aren't good at threaded programming or whether threaded programming is inherently harder.


I think the model we use is wrong. My current belief is that the probability of races and deadlocks occurring is a function of the amount of shared data and that conventional shared-everything threading implementations make this far more common than it ought to be. In its extremus this points to a distribution-capable asynchronous message passing architecture á la Erlang, or at least something like Hoare's CSP.

The related criticism that there's too much mutable data is correct in many ways, but can also be seen as a way of reducing the "data frontier"; if it's immutable then there are no concurrency implications.

Now, that said, transactional models don't fit well into that mental model. There can be problems with them--write sync was mentioned in the article--but they seem to be less inherently buggy.


doesnt this inherently lower performance?

Message-passing rather than shared data structures implies you need to pass around heavy copies of data.

Are Lock-free data structures (supported probably by atomic hardware operations) the middle ground?

http://www.audiomulch.com/~rossb/code/lockfree/


Not necessarily, even on a uniform memory access machine; perhaps you pass around a reference to an object that is located close to the data you want. On a non-uniform memory access machine (which is to say all of them these days when you take cache into account) message passing and the message passing formalism can actually save you time.

Anyway, I don't a priori object to shared data structures, but it is important to remember that they are inherently dangerous and the amount of shared memory should be sharply limited to that which is actually necessary, rather than blindly making everything shared.

Lock free data structures won't protect you from deadlocks or races necessarily; they mean you won't have to lock, but you can easily accidentally write a situation with two processes mutually waiting on each other.


You might even harness the power of paging in order to pass messages, with a copy-on-write exception to make the actual copying as lazy as you can. And if you're working on page boundaries copying can be done using DMA controllers.


Along with what ghpaco said, keep in mind that locking isn't free either.




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

Search: