Hi there, On Tuesday, November 4, 2014 8:05:03 AM UTC+1, Daniel Marjenburgh wrote: > > I know transients aren't bash-in-place, as they may return new references, > but that is not the problem I'm having here. > If you bash in place, you would get unexpected results, but still the same > result every time. The problem here is that the 'value' of the transient is > changing underneath you because of another thread. >
To ensure consistency, you must ensure that you update a transient value at most once – all calls (including reads) on the same transient value after an update is illegal/undefined behaviour. Transient updates – minus persistent! – will always return a new transient value, even though the transient returned may be the same reference. So the code (let [v (transient {:a 0}) f1 (future (reduce #(assoc! % (+ (:a %) %2)) v (range 10))) f2 (future (reduce #(assoc! % (+ (:a %) %2)) v (range 10)))] @f1 @f2 ; wait for futures (persistent! @f1)) will result in undefined behaviour, because the original v is updated twice (and is potentially read after an update). The correct way would be to do the following: (let [v {:a 0} f1 (future (reduce #(assoc! % (+ (:a %) %2)) (transient v) (range 10))) f2 (future (reduce #(assoc! % (+ (:a %) %2)) (transient v) (range 10)))] @f1 @f2 ; wait for futures (persistent! @f1)) I don't see an explicit rule at http://clojure.org/transients stating this, although I feel it is implicit from the bash in-place rule. Perhaps that's something worth adding to the page to avoid confusion. -- Regards, Jean Niklas L'orange > Op dinsdag 4 november 2014 06:13:00 UTC+1 schreef Alex Miller: >> >> >> >> On Monday, November 3, 2014 10:44:19 PM UTC-6, Atamert Ölçgen wrote: >>> >>> Thanks Alex! >>> >>> Now that I took a second look at Daniel's code, it seems assoc! is used >>> like swap!, as if it would modify m in place. So I would expect, if it runs >>> without errors, result to be {:a 0}. >>> >> >> Right, that is an incorrect usage - it will actually modify with changes >> though, but not in expected ways (this is independent of the change we're >> discussing - you can get the same behavior in a single thread modifying a >> transient without reusing the return). >> >> Given that transients are values (not reference types like ref or atom) I >>> can't think of a case where they can be modified concurrently. >>> >> >> You can, but not without going pretty far out of normal Clojure code. >> >> >>> >>> >>> On Tue, Nov 4, 2014 at 11:19 AM, Alex Miller <al...@puredanger.com> >>> wrote: >>> >>>> >>>> >>>> On Monday, November 3, 2014 9:00:10 PM UTC-6, Atamert Ölçgen wrote: >>>>> >>>>> >>>>> >>>>> On Mon, Nov 3, 2014 at 5:57 PM, Daniel Marjenburgh < >>>>> dmarje...@gmail.com> wrote: >>>>> >>>>>> Hi, >>>>>> >>>>>> I just want to address this issue (CLJ-1498 >>>>>> <http://dev.clojure.org/jira/browse/CLJ-1498>). It was accepted in >>>>>> 1.7-alpha2 and I haven't seen a lot of discussion around it, even though >>>>>> it's quite a big change. >>>>>> >>>>>> With this change the following code is possible: >>>>>> >>>>> >>>>> With persistents the result would be the same, every time. If this is >>>>> now valid Clojure, I didn't run it myself, we are sacrificing >>>>> consistency. >>>>> I don't understand what we're getting in return. >>>>> >>>>> Even the simple example in the ticket (with one future) doesn't make a >>>>> lot of sense to me. >>>>> >>>>> Am I missing something obvious? >>>>> >>>> >>>> Transients always expect thread isolation. In the past this was locked >>>> to a single thread (the one that made the transient). That restriction now >>>> extends to being used by multiple threads, but isolation should still be >>>> maintained for proper use. >>>> >>>> What we are gaining is the ability to use transient collections in go >>>> blocks with core.async, where the thread being used is just one pool out >>>> of >>>> a thread. For example (just typing this not running it, so excuse any >>>> typos): >>>> >>>> (defn drain [ch coll] >>>> (let [t (transient []] >>>> (go-loop >>>> (if-some [v (<! ch)] >>>> (do (conj! t v) (recur)) >>>> (persistent! t))))) >>>> >>>> This doesn't necessarily work in core.async because each time the <! >>>> parks, it may be woken up with a different thread from the pool. The >>>> transient change allows this kind of code to succeed. >>>> >>>> >>>>> >>>>>> (let [m (transient {:a 0}) >>>>>> futs (for [n (range 100)] >>>>>> (future (assoc! m :a (inc (:a m)))))] >>>>>> (mapv deref futs) ; wait until futures are done >>>>>> (persistent! m)) >>>>>> >>>>>> >>>>>> The results will vary per run, where it used to throw >>>>>> an IllegalAccessError: "Transient used by non-owner thread". >>>>>> >>>>>> I understand the problems of not being able to have 1 go routine >>>>>> access a transient, even though it would be safe, but this solution >>>>>> feels >>>>>> like it's throwing out the baby with the bathwater. Basically, it's >>>>>> doing >>>>>> away with what the following block on clojure.org >>>>>> <http://clojure.org/transients> says: >>>>>> >>>>>> *Transients enforce thread isolation**.* Because each result of a >>>>>>> transient operation shares (mutable) structure with the previous, it >>>>>>> would >>>>>>> be very dangerous if more than one thread were to manipulate a >>>>>>> transient at >>>>>>> once. In order to prevent this, transients will detect any (read or >>>>>>> write) >>>>>>> use from a thread other than the one that created them and throw an >>>>>>> exception. >>>>>> >>>>>> >>>>>> >>>>>>> >>>>>>> This may not sound like a concurrency story, but single-thread >>>>>>> isolation is actually a very useful concurrency semantic. The whole >>>>>>> point >>>>>>> of using transients is that doing so is safe, because their use is an >>>>>>> isolated implementation detail of otherwise functional code. Having >>>>>>> that be >>>>>>> enforced means that some things that would normally be very hard to >>>>>>> make >>>>>>> safe with ordinary mutable data structures become easy. >>>>>> >>>>>> >>>>>> I don't have a good solution for dealing with transients and logical >>>>>> threads, but I would much prefer keeping the semantics of transients as >>>>>> they are and maybe pass an option to transient to disable owner checking. >>>>>> >>>>>> -- >>>>>> 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 >>>>>> --- >>>>>> You received this message because you are subscribed to the Google >>>>>> Groups "Clojure" group. >>>>>> To unsubscribe from this group and stop receiving emails from it, >>>>>> send an email to clojure+u...@googlegroups.com. >>>>>> For more options, visit https://groups.google.com/d/optout. >>>>>> >>>>> >>>>> >>>>> >>>>> -- >>>>> Kind Regards, >>>>> Atamert Ölçgen >>>>> >>>>> -+- >>>>> --+ >>>>> +++ >>>>> >>>>> www.muhuk.com >>>>> >>>> -- >>>> 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 >>>> --- >>>> You received this message because you are subscribed to the Google >>>> Groups "Clojure" group. >>>> To unsubscribe from this group and stop receiving emails from it, send >>>> an email to clojure+u...@googlegroups.com. >>>> For more options, visit https://groups.google.com/d/optout. >>>> >>> >>> >>> >>> -- >>> Kind Regards, >>> Atamert Ölçgen >>> >>> -+- >>> --+ >>> +++ >>> >>> www.muhuk.com >>> >> -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@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+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.