7 ms·
> If you need a lock, then you probably have global mutable state that has to be protected against concurrent updates. The existence of global mutable state ind
by sz4kerto 13y ago
> If you need a lock, then you probably have global mutable state that has to be protected against concurrent updates. The existence of global mutable state indicates a flaw in the application’s design, which you should review and change.
No, no, no. If you are writing multithreaded programs, you _will_ have locks, period. The question is whether you explicitly use them or if they are hidden somewhere in your libraries, frameworks, etc. Actually, the latter are much more difficult to debug. Most of us have probably seen situations where deadlocks or data corruption has occured because of some extremely complicated interaction of message pumps - UI thread - callbacks - async operations - etc., without any explicit locking.
> Perhaps most importantly, modern programming languages and libraries make it easy to create a producer-consumer application.
Yes, that's great again, until the deadlock-corruption-indeterminism problem comes back again in a different abstraction level.
There is _no way out_. If you are writing concurrent apps, you have understand what's happening. By concurrent I mean concurrent on the level you are working on. Using a parallel 'map' operation is different because the concurrency is an implementational detail from your perspective. But if you are writing a massively concurrent quoting/trading engine, then you have to know what's happening. And if you do, then you're free to use locks, executors, async, whatever -- it's just a tool to solve the problem.
...
The more I look at this article the more ridiculous it seems...
- temujin 13y ago"If you are writing multithreaded programs, you _will_ have locks, period." Not necessarily. In scientific computing, it is common to encounter "embarrassingly parallel" scenarios where each thread can solve equal-sized chunks of the problem, and you can wait till all threads have terminated to collect results. edit: Okay, the final wait is usually implemented with a lock under the hood. Nevertheless, this type of program is straightforward to reason about.
- sz4kerto 13y agoAnd of course that 'wait' operation does not involve locks and/or condvars. Oh wait.
- lmm 13y agoWho cares how it's implemented? Manually using a lock is a smell in the same way that goto is a smell. Wait for a task to finish is not a smell in the same way that while is not a smell.
- eXpl0it3r 13y agoThere isn't really that big of a difference between a lock and a wait, which is the point that was being made. It either way will "lock" up the thread until condition X is full filled and since it depends on other threads to finish, it's still very possible to get deadlocks and a like. Besides that, depending on the task, it's very important how something is implemented.
- mannykannot 13y ago"Manually using a lock is a smell in the same way that goto is a smell." The presence of a lock tells you nothing about whether a program is well-designed. In fact, the whole idea that you can argue about the correctness of a program from the presence or absence of "code smells" is absurdly simplistic. I have seen plenty of bad code littered with while statements.
- the_af 13y agoCode smells (I don't like the name either) are just warning signs. They do not necessarily indicate an actual problem. Incorrect use of abstraction levels, such as using tools that are too low-level and error-prone for the task at hand (as in the majority of business applications) IS one of those warning signs. It usually indicates a conceptual problem, and makes the code needlessly complex.
- yxhuvud 13y agoThat can just as well be implemented by message passing, which can happen through a socket or whatever.
- fr0sty 13y ago
- mnw21cam 13y agoHere is where you need to be really careful. How do you know all the threads have terminated? That's communicated through a lock in the underlying . Certainly some systems (such as Java and multiple CPUs with independent caches) that use thread-local working areas, you cannot rely on a task being completed meaning that you can actually use the data it has produced, until you actually make use of a lock. The lock ensures that the local copy is synchronised with the central copy. There's plenty of opportunity for fun if you don't use locks in some form or other in multi-threaded systems.
- polskibus 13y agoSurely the wait is a kind of a lock?
- mannykannot 13y ago"Nevertheless, this type of program is straightforward to reason about." This type of program is an uninteresting corner case, that tells us nothing about the more general problem of reliably writing concurrent software.
- balakk 13y agoPerhaps you might appreciate that there is a wide spectrum of problems that might require parallelism, from dramatically simple enterprise apps to "massively concurrent quoting/trading engines". This advice is written for the 90% of use-cases that can be solved with simple abstractions, where programmers are still spinning up threads and locking objects. Most application programmers don't do manual memory management, low-level network programming, graphics, and so on. Why should parallel programming be any different? That is the point I believe the author is making, and I happen to agree.
- mcguire 13y agoOn the other hand, there is a certain undercurrent to this and many similar posts that I've seen, something along the lines of "Because I don't need to do it, no one needs to." Or, in this case, "Synchronization primitives are deep, dark, evil voodoo magic and you'll never be beardy enough to do it right." Such as: "Locks and other synchronization primitives are systems level constructs. People who know a lot more about multithreading use those constructs to build concurrent data structures and higher level synchronization constructs that mere application programmers like you and I use in our programs." ...which is probably true, but there is nothing scary about "systems level" stuff---even the author could probably tool up to do it. That becomes a problem in the 10% of use-cases (which I suspect is more frequent than that) where the simple abstractions are not satisfactory.
- jwr 13y ago> No, no, no. If you are writing multithreaded programs, you _will_ have locks, period. The question is whether you explicitly use them or if they are hidden somewhere in your libraries, frameworks, etc. Actually, the latter are much more difficult to debug. You say that and then proceed to call the article ridiculous. Well, I write and maintain large applications in Clojure. The code uses a multitude of threads for various purposes, with different lifetimes. And I've yet to see a single explicit lock. What's more important, though, is that I've yet to see a race condition, a deadlock, or data corruption, which I find pretty impressive. I do agree that you need to understand what is happening — but I disagree with your assumption that the basic tools are all on the same level. They are not, which Clojure clearly demonstrates. I won't go into details, because Rich Hickey explains it much, much better than I ever will be able to — here's an example of his talk: http://www.youtube.com/watch?v=dGVqrGmwOAw http://www.youtube.com/watch?v=dGVqrGmwOAw
- eXpl0it3r 13y agoI guess that points out another issue with the article - it talks about programming in general and doesn't take into account that it's way easier to write multi-threaded applications with languages that are explicitly created for developing multi-threaded application and languages that just happen to support threading. So comparing for example Clojure and C++ in regards to multi-threading Clojure will of course win, since C++ only recently got official standardized threading implemented and is far away from a really good concurrency support.
- nly 13y agoC++ will, of course, win the moment you let 3rd party libraries in the door. Oh, sorry... that's just another vague generalisation.
- sz4kerto 13y agoI'll definitely watch the video and I have to say I don't know Clojure very well. I think we need to distinguish between in-app race conditions (a primitive example is when you're trying to modify a data which cannot be written atomically), and situations where you have to maintain some order of operations because if you don't then the program won't work correctly _from the end-user's standpoint_. In concurrent programming safe operations are not composable, i.e. if you have >1 thread-safe operations then using those operations in parallel won't be thread-safe by itself). What I tried to say is that as you increase the level of abstraction (i.e. build something useful using the great constructs of Clojure), if concurrency still exists on that higher abstraction level then you need to think about synchronization, again. And the problem you'll face is really similar to those low-level issues when you try to protect a 64-bit long data structure in C. That's why I encourage everybody to think about these simple toy-problems even if they specifically never come up when you write programs using modern tools.
- exDM69 13y ago> If you need a lock, then you probably have global mutable state that has to be protected against concurrent updates. The existence of global mutable state indicates a flaw in the application’s design, which you should review and change. There's a grain of truth in this one. A lot of people think that multithreaded programming is like single threaded programming with locks added. This is not the case. In my experience, with almost every mutex there should be one or more condition variables that are used to signal various things. A single mutex is almost never enough to achieve anything meaningful, because for every state you mutate, there's probably another thread waiting for that state to be mutated. If you're working with a language or a framework that has some kind of queues and channels (like Go or Haskell, for example), they are probably more useful for lots of tasks than locks and conditions. Of course, someone has implemented those queues using locks and conditions, or atomic operations.
- eXpl0it3r 13y agoI can only agree here, though I think the article would be much better, if the author had clearly specified his universe of applications. > We move data around, transform it, perhaps do some calculations from time to time, and finally store the results in a database or display them on the screen. If it's just limited to this, then I could partially agree. But I can't agree at all to the following statement: > There are difficult multithreading scenarios, just as there are difficult scenarios in the single-threaded world. But those are relatively rare. Adding some "magic" patterns and guidelines won't make synchronization issues go away.
- kasey_junk 13y agoIt's worse than that. On commodity hardware you can't prevent having global state because the processor is making those choices for you. L2/L3/Main Memory are all mutable global states that your data gets put into. Of course, modern processors are pretty good at making sure they don't corrupt that global state, but drill down far enough & you have global mutable state, that needs to be synchronized around. That said, if you aren't at that level you can almost certainly write lock free code (and you should) by apply design principals.
- the_af 13y agoIsn't this basically what TFA is saying? Who is talking about the hardware level?
- rietta 13y ago"...No, no, no. If you are writing multithreaded programs, you _will_ have locks, period. The question is whether you explicitly use them or if they are hidden somewhere in your libraries, frameworks, etc...." That is not true. Lock-free data structures exist and are applicable to many multi-threading use cases. For a super primer see https://en.wikipedia.org/wiki/Non-blocking_algorithm https://en.wikipedia.org/wiki/Non-blocking_algorithm and if you are really interested, read about Transactional memory - http://dl.acm.org/citation.cfm?id=165164 http://dl.acm.org/citation.cfm?id=165164, and then "The Art of Multiprocessor Programming" (it is a text book) by Herlihy and Shavit.
- sz4kerto 13y agoI have been following transactional memory since 2004 or something like that, it's great as it solves the locking problem on that specific abstraction level. It does not solve the general problem of synchronization, though. See my other comment in this thread. Locking problem != making sure that a 64-bit value will be left in an inconsistent state.
- rietta 13y agoYou have followed it longer than I have then. My gut reaction is more to the universal nature of your claim that I quoted. It is a goal to have practical large systems that need no locks. Current operating systems in production may not be there yet, but it is not proven that it is impossible to create a system that is entirely lock free.
- mannykannot 13y agoReasoning about the correctness of lock-free concurrency is much more difficult than doing the same for sequential algorithms, and gives the lie to the original author's claim that successful concurrent programming is just a matter of avoiding mutable global state.
- fulafel 13y agoThe quote seems to use "lock" to mean any locking provided by your primitives, he probably includes the atomic instructions too since they have locking in hardware.
- Hermel 13y ago> There is _no way out_. If you are writing concurrent apps, you have understand what's happening. Actually, there is a number of best practices that help you avoiding problems. For example, it helps to have a strict ordering of locks - i.e. everyone who locks on objects A and B, must do so first on object A. As a corollary, you should not call a callback or notify a listener while holding a lock - because you don't know what it will do. These two simple rules together already help a lot - even without understanding every possible state.
- the_af 13y ago> No, no, no. If you are writing multithreaded programs, you _will_ have locks, period It's clear from the article the author means low-level locking primitives.