On Dec 10, 2012, at 5:39 AM, Marko Topolnik wrote:
I personally have never used STM in nontrivial ways (AFAIC), but that's due more to the demands of the problems I run into more than anything else. On the other hand, I have used, abused, and benefitted from agents in umpteen ways. Actually, I have often done things using agents that might otherwise been done using STM or other similar approaches, simply to ensure that:
(a) the processing involved can be readily parallelized, and
(b) if necessary, the system can be partitioned/distributed with minimal impact to the architecture, since — if you're careful about things — it doesn't matter whether a send is evaluated in an in-process agent or one housed in a different server/VM/whatever
It's true that STM is "all or nothing", but it is so over the scope of refs you choose. If there's some side-effecting bit you need to do somewhere, then clearly that's not going to fit within a transaction…but that bit will often fit just fine in a send-off to an agent provoked _by_ a transaction.
> My guess is, if your task is something purely computational and amenable to massive parallelization, you may have a go with STM; if it's just about business logic accessible concurrently by many clients, you won't find it workable.
If your task is purely computational and amenable to massive parallelization, you _should_ use agents whenever possible. STM provides for coordination in order to enforce consistency; unless all of your operations are commutative (in which case, you should probably be using agents anyway), a program using STM _will_ provoke retries and other means to route around ref contention. This is acceptable because STM is all about maintaining correctness in the face of concurrent mutation, and not necessarily about performance, aggregate throughput, and so on.
On the other hand, ref readers are _never_ blocked (regardless of what's going on on the write side), so the data in such refs is always accessible. This sounds like an ideal combination for "business logic" (as nebulous a term as that is) to me.
I'd be surprised if Paul doesn't hear from people directly
But concurrency is all about performance and throughput. So where is the benefit of using correct, slow concurrent mutation? I guess in a write-seldom, read-often scenario.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
It's true that STM is "all or nothing", but it is so over the scope of refs you choose. If there's some side-effecting bit you need to do somewhere, then clearly that's not going to fit within a transaction…but that bit will often fit just fine in a send-off to an agent provoked _by_ a transaction.send-off fails to be useful whenever you need the results within the transaction (quite often, that is).
> My guess is, if your task is something purely computational and amenable to massive parallelization, you may have a go with STM; if it's just about business logic accessible concurrently by many clients, you won't find it workable.
If your task is purely computational and amenable to massive parallelization, you _should_ use agents whenever possible. STM provides for coordination in order to enforce consistency; unless all of your operations are commutative (in which case, you should probably be using agents anyway), a program using STM _will_ provoke retries and other means to route around ref contention. This is acceptable because STM is all about maintaining correctness in the face of concurrent mutation, and not necessarily about performance, aggregate throughput, and so on.But concurrency is all about performance and throughput. So where is the benefit of using correct, slow concurrent mutation? I guess in a write-seldom, read-often scenario.
On the other hand, ref readers are _never_ blocked (regardless of what's going on on the write side), so the data in such refs is always accessible. This sounds like an ideal combination for "business logic" (as nebulous a term as that is) to me.Business logic almost always involves communication with outside systems (since it's usually about integration of many existing systems). Even if not, a scalable solution must be stateless (a prerequisite for cluster deployment) and any durable state must go into a single datasource common to all cluster nodes. Again, these datasources don't participate in an STM transaction. Maybe this would be a major route of improvement: integrate the STM with external datasource transactions. But this is still quite removed from the present.
On 10 Dec 2012, at 13:37, Marko Topolnik <marko.t...@gmail.com> wrote:But concurrency is all about performance and throughput. So where is the benefit of using correct, slow concurrent mutation? I guess in a write-seldom, read-often scenario.I'm not at all sure that that's true. There are plenty of occasions where concurrency is about being able to do more than one thing at a time, and not necessarily about making something faster.For example, your mobile 'phone is concurrent because, while it's playing music to you, it also wants to notice when you poke the screen and listen for incoming calls/messages from the network. And your IDE is concurrent so that it can check the syntax of your code in the background while the UI remains responsive.
On Dec 10, 2012, at 8:37 AM, Marko Topolnik wrote:It's true that STM is "all or nothing", but it is so over the scope of refs you choose. If there's some side-effecting bit you need to do somewhere, then clearly that's not going to fit within a transaction…but that bit will often fit just fine in a send-off to an agent provoked _by_ a transaction.send-off fails to be useful whenever you need the results within the transaction (quite often, that is).I'm not aware of any system that provides transactional semantics in the face of in-transaction side-effecting actions. If you can refer me to any, that'd be great.
But concurrency is all about performance and throughput. So where is the benefit of using correct, slow concurrent mutation? I guess in a write-seldom, read-often scenario.Fundamentally, concurrency is about simultaneous independent computation. Depending on the domain and computations involved, single-thread performance and aggregate throughput can vary significantly.Anyway, read-heavy applications are still the norm in most industrial settings, despite the rise in popularity of write-scalable architectures.
Business logic almost always involves communication with outside systems (since it's usually about integration of many existing systems). Even if not, a scalable solution must be stateless (a prerequisite for cluster deployment) and any durable state must go into a single datasource common to all cluster nodes. Again, these datasources don't participate in an STM transaction. Maybe this would be a major route of improvement: integrate the STM with external datasource transactions. But this is still quite removed from the present.I'm certain that particular set of requirements holds in certain settings, but they are hardly universal.If I may make a tenuous inference, it sounds like you're trying to fit every state transition within an application into a transaction. If so, I'd recommend the opposite: decomposing applications and their processes into modular bags of state and treating them separately will lead to big wins — including potentially being able to use e.g. STM in one place, and agents in another, each interacting with the other as necessary.
Re: getting disparate datasources to participate in transactions, you might want to take a look at Avout:I can't say I've used it, but it is at least an existence proof of the ability of the Clojure STM model to be distributable.
Just curious, how did you immediately eliminate the possibility that the reducing function was mutating the list that is being reduced? No concurrency involved. In regular Java the 95% leading cause of CME is precisely that.Anyway, this applies to immutable structures per se, whether combined with atoms, refs, or none. But a full wartime story must also cover how the solution avoids the pitfalls of retryable transactions. This is the real sore point in my experience, and the one which makes STM an all-or-nothing enterprise.
On Tuesday, December 11, 2012 8:53:40 PM UTC+1, stuart....@gmail.com wrote:Hi Paul,Here is a real-world, production software example of the advantage of values+refs over mutable objects and locks. A Datomic user reported the following stack trace as a potential bug:12:45:43.480 [qtp517338136-84] WARN c.v.a.s.p.e.UnknownExceptionHandler - UnknownExceptionHandler: null
java.util.ConcurrentModificationException: null
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819) ~[na:1.7.0_07]
at java.util.ArrayList$Itr.next(ArrayList.java:791) ~[na:1.7.0_07]
at clojure.core.protocols$fn__5871.invoke(protocols.clj:76) ~[clojure-1.4.0.jar:na]
at clojure.core.protocols$fn__5828$G__5823__5841.invoke(protocols.clj:13) ~[clojure-1.4.0.jar:na]
at clojure.core$reduce.invoke(core.clj:6030) ~[clojure-1.4.0.jar:na]
I immediately had 99% confidence that the bug was in user code, and even a pretty good idea what went wrong. A call to "reduce" is a functional transformation, and it expects to be passed values. The exception clearly indicates a violation of that contract, and is caused by cross-thread aliasing and mutation in the calling code.Regards,Stu
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
Just curious, how did you immediately eliminate the possibility that the reducing function was mutating the list that is being reduced? No concurrency involved. In regular Java the 95% leading cause of CME is precisely that.
Anyway, this applies to immutable structures per se, whether combined with atoms, refs, or none. But a full wartime story must also cover how the solution avoids the pitfalls of retryable transactions. This is the real sore point in my experience, and the one which makes STM an all-or-nothing enterprise.
Hi Paul,Here is a real-world, production software example of the advantage of values+refs over mutable objects and locks. A Datomic user reported the following stack trace as a potential bug:12:45:43.480 [qtp517338136-84] WARN c.v.a.s.p.e.UnknownExceptionHandler - UnknownExceptionHandler: null
java.util.ConcurrentModificationException: null
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819) ~[na:1.7.0_07]
at java.util.ArrayList$Itr.next(ArrayList.java:791) ~[na:1.7.0_07]
at clojure.core.protocols$fn__5871.invoke(protocols.clj:76) ~[clojure-1.4.0.jar:na]
at clojure.core.protocols$fn__5828$G__5823__5841.invoke(protocols.clj:13) ~[clojure-1.4.0.jar:na]
at clojure.core$reduce.invoke(core.clj:6030) ~[clojure-1.4.0.jar:na]
I immediately had 99% confidence that the bug was in user code, and even a pretty good idea what went wrong. A call to "reduce" is a functional transformation, and it expects to be passed values. The exception clearly indicates a violation of that contract, and is caused by cross-thread aliasing and mutation in the calling code.Regards,Stu
--
Many Grettings
John
I wouldn't say that it should not have been added since its presence isn't harming anything. You could say, though, that rarely anyone would realize something was missing if Clojure didn't have the STM.
Another concurrency model I've used a great deal is the tuplespace model, specifically javaspaces. This is an often forgotten model that has a lot to offer with a high expressiveness to complexity ratio.
Not closure specific, so feel free to contact me again directly if you're interested.
Although I am convinced that STM can solve things that locks cannot (See the claim "lock-based programs do not compose" on Wikipedia page http://en.wikipedia.org/wiki/Software_transactional_memory), I feel this feature is so much over-sold. Whenever you read someone raves about Clojure on the web, they mention "STM" as a key feature and how wonderful it is. My own experience is similar to yours, atoms work most of the time and I also need to use locks. I benefit more from the fact that Clojure clearly marks what is mutable and what is not than any of those "advanced" features.
Another concurrency model I've used a great deal is the tuplespace model, specifically javaspaces. This is an often forgotten model that has a lot to offer with a high expressiveness to complexity ratio.
My recommendation is either "Persistent Datastructures" or "Database as a Value"
If you can't incorporate novelty without cloning the
entire datastructure, thats not that useful.
In the epic thread about the STM between Rich and Cliff Click[1] the
main argument against the STM was that it didn't help solve the problem
of where to place guards around the data. From one of Cliff's arguments:
In a trivial example I can say �go up one call level and atomic there�,
but in the Real Program � I can�t do that.
Go up how many layers and add atomic? 1 layer? 10 layers? 100 layers?
Yes, I see unique call-stacks
with >100 layers. I can�t put atomic around main because that makes my
program single-threaded.
I believe Cliff is arguing here that when a program pushes all of the
state into a single atom where a lot of writes occur that app
effectively is single-threaded. (Please correct me if I am
misunderstanding!) Thoughts?
This is exactly the trap MPI fell into; and you have to do it anyways. Double-unsmiley. :-( :-(
Here’s the deal:
I write a Large Complex Program, one that Really Needs STM to get it right.
- But performance sucks.
- So I do a deep dive into the STM runtime, and discover it has warts.
- So I hack my code to work around the warts in the STM.
- Crap like: at an average of 5 cpus in this atomic block the STM ‘works’, but at an average of 7 cpus in the same atomic block I get a continous fail/retry rate that’s so bad I might as well not bother. So I guard the STM area with a “5-at-a-time” lock and a queue of threads waiting to enter. Bleah (been there; done that – for a DB not an STM but same-in-priniciple situation). A thousand thousand variations of the same crap happens, each requiring a different hack to my code to make it performant.
- Meanwhile the STM author (You: Rich) hacks out some warts & hands me a beta version.
- I hack my code to match the STM’s new behavior, and discover some new warts.
Back & Forth we go – and suddenly: my app’s “performance correctness” is intimately tied to the exact STM implementation. Any change to the STM’s behavior kills my performance – and you, Rich, have learned a lot about the building of a robust good STM. You (now) know the mistakes you made and knowit’s time to restart the STM implementation from scratch.
On Dec 14, 2012, at 12:55 AM, Paul Butcher wrote:Rich - what is the "soundbite description" of Clojure's concurrency model you're happiest with?
Ah, soundbites, the foundation of modern programmer education :)
Certainly, pigeonholing Clojure as STM-based is way off the mark.
Also, a chapter on STM that doesn't distinguish Clojure and Haskell's functional STM approach from the ordinary "wrap your old imperative code in transactions" approach is going to miss the biggest point.
I guess this would be my alternative chapter title proposal:
Functional Programming + Reference Types
When you choose an atom you are effectively saying that nobody else will ever need to ensure consistency between this identity and another. This is fine if you're writing an application, where you can stake a claim and create an atom for global state, but what when a library you use does that? Or maybe you're writing a library? If you choose an atom, might you be forcing a decision that belongs with the application?
Yes, I know Clojure libraries typically don't expose identities and state in this manner, as typically you'd just want to provide the functional stuff and let the application manage the state, but surely there are some libraries where this is done and perhaps desirable?
In these cases, (do they exist?) where you want to expose an identity to others, should you not be using a ref?