Comment by gf000

14 hours ago

What is a safe concurrency model? Like, actors can trivially deadlock/livelock, they are no panacea at all, and are trivial to recreate (there are a million java implementations)

You make it sound like there is some modern development superseding what java has, but that's absolutely not the case.

Like even rust is just pretty much a no-overhead `synchronized` on top of an object. It is necessary there, because data races are a fundamental memory safety issue, but Java is immune to that (it has "safe" data races). Logical bugs can trivially happen in either case - as an easy example even if all your fields are atomically mutated, the whole object may not make sense in certain states, like a date with February the 31st. Rust does nothing against such, and concurrent data structures have ample grounds for realistic examples of the above.

> What is a safe concurrency model?

STM.

The terms 'atomic', 'thread-safe', and 'concurrent' collections are thrown around too loosely for application programmers IMO, for exactly your example above.

In other scenarios, 'atomics' refer to the ability to do one thing atomically. With STM, you can do two or more things atomically.

Likewise with 'thread-safe'. Thread-safe seems to indicate that the object won't break internally in the presence of multiple threads, which is too low of a bar to clear if your goal is to write an actually thread-safe application out of so-called 'thread-safe' parts.

STM has actual concurrent data structures, where you can write straight-line code like 'if this collection has at least 5 elements, then pop one'.

I don't think the Feb 31 example is that fair though, because if you want to construct a representation of Feb 31, who's going to stop you? And if you don't want to, plain old static types is the solution.

  • I couldn't give a better reply than this author:

    https://joeduffyblog.com/2010/01/03/a-brief-retrospective-on...

    Also, a phenomenal writing (as are his other posts) on the whole concurrency landscape, see:

    > A wondrous property of concurrent programming is the sheer number and diversity of programming models developed over the years. Actors, message-passing, data parallel, auto-vectorization, …; the titles roll off the tongue, and yet none dominates and pervades. In fact, concurrent programming is a multi-dimensional space with a vast number of worthy points along its many axes.

    • I've read a few postmortems about STM. I have to take them with a grain of salt because I usually read those reports right after doing a bunch of STM programming, and right before doing a bunch more STM programming. Reports of its death have been greatly exaggerated.

      Here it is in 2006 featuring the same Tim from your article: https://www.youtube.com/watch?v=tve57vilywc

      I didn't start using it in anger till 2013-2014 maybe? But I don't recall any major differences between what the video shows and how it works in 2025.

      Anyway, postmortems usually boil down to two issues:

      1) That's not how programmers usually do it

      2) We couldn't pull it off

      The most obvious explanation for 1 is 2. I, too, would be disappointed by the low-adoption rates of my new technology if I hadn't built it or released it to users.

      But the article has some gems:

        Transactions unfortunately do not address one other issue, which turns out to be the most fundamental of all: sharing. Indeed, TM is insufficient – indeed, even dangerous – on its own because it makes it very easy to share data and access it from multiple threads;
      

      I cannot read this charitably. This is the only reason for, not a damning reason against. It's like doing research & development on condoms, and then realising it's a hopeless failure because they might be used for dangerous activities like sex.

        I already mentioned a great virtue of transactions is their ability to nest. But I neglected to say how this works. And in fact when we began, we only recognized one form of nesting. You’re in one atomic block and then enter into another one. What happens if that inner transaction commits or rolls back, before the fate of the outer transaction is known
      

      You nest transactional statements, not the calls to atomic. The happy-path for an atomic is that it will commit; it should be obvious a priori that something that commits cannot be in the codepath that can be rolled back.

        Then that same intern’s casual statement pointing out an Earth-shattering flaw that would threaten the kind of TM we (and most of the industry at the time) were building. ...
        An update in-place system will allow that transaction to freely change the state of x. Of course, it will roll back here, because isItOwned changed to true. But by then it is too late: the other thread using x outside of a transaction will see constantly changing state – torn reads even – and who knows what will happen from there. A known flaw in any weakly atomic, update in-place TM.
      
        If this example appears contrived, it’s not. It shows up in many circumstances.
      

      I agree that it's not contrived. It's in the problem-space of application writers. It's not a problem caused by introducing STM. We want an STM system to allow safe access to isItOwned & x, because it's a PITA to try to do this with locks.

      1 reply →

> the whole object may not make sense in certain states

"Make invalid states unrepresentable" - it's bad design that February the 31st is a thing in your data structure when that's invalid. You can't always avoid this, but it's appalling how bad most people's data structures are.

C's stdlib provides a tm structure in which day of the week is stored in a signed 32-bit integer. You know, for when it's the negative two billionth day of the week...

  • This is more of a toy example for how a set of atomic changes can still end up in an inconsistent state, e.g. setting January the 31st and February 3rd in quick succession from two or more different threads may result in Feb 31st being visible from a third thread. This is not solved by Rust and your struct will even get the Sync trait automatically, which may be not be applicable as in this case.

    • Given your example, I am convinced you've never written any Rust. Of course it does stop you doing shit like that. But in this example, even Java does it properly, since the constructor runs to completion before any Object is accessible to any Thread, not just the one creating it. You need to validate the state of the object in the constructor to prevent that, but TBH why are we talking about this, it's almost completely unrelated to concurrency models.

      5 replies →

  • > “Make invalid states unrepresentable”

    I think this phrase sounds good but is not applicable to systems that touch messy reality.

    For example, I think it’s not even possible to apply it to the `tm` structure, as leap seconds are not known in advance.

    • I agree that messy reality can intervene, in the medium term (for about a decade) we'll need to handle leap seconds

      But we can do a lot without challenging the messy reality. 61 second minutes are (regrettably) a thing in some time systems, but negative 1 million second minutes are not a thing, there's no need for this to be a signed integer!

      3 replies →

> Like, actors can trivially deadlock/livelock,

Oh my ... you never seen a proper Actor language, have you?

Have a look at Erlang and Pony, for starters. It will open your mind.

This in particular is great: https://www.ponylang.io/discover/what-makes-pony-different/#...

> Pony doesn’t have locks nor atomic operations or anything like that. Instead, the type system ensures at compile time that your concurrent program can never have data races. So you can write highly concurrent code and never get it wrong.

This is what I am talking about.

> You make it sound like there is some modern development superseding what java has, but that's absolutely not the case.

Both Actor-model languages and Rust (through a surprisingly different path: tracking aliases and lifetimes) do something that's impossible in Java (and most languages): prevent data races due to improper locking (as mentioned above, if your language even has locks and it doesn't make them safe like Rust does, you know you're going to have a really hard time. actor-languages just eliminate locks, and "manual concurrency", completely). Other kinds of races are still possible, but preventing data races go a very, very long way to making concurrency safe and easy.

  • Does preventing data races (which is not particularly hard if you are willing to give up certain properties, e.g. just immutability alone solves it) that much of a win?

    You just made a bunch of concurrent algorithms un-implementable that would give much better performance for the benefit of.. having all the other unsolvable issues with concurrency? Like, all the same issues are trivially reproducible at a higher level, with loops within actors' communication that only appear under certain, very dynamic conditions, or a bunch of message passing ending up in an inconsistent state, just not on an "object" level, but on a "group of object" level.

  • Perhaps there is some confusion here between data races and race conditions. Rust and Pony prevent data races, but not race conditions.