upvote
C++ committee members (mainly Boehm, but also Stroutstrup) also deserve a lot of credit for giving us a well specified memory model for concurrency outside of functional languages. Rust basically copy/pasted the C++11 memory model wholesale. Rust's fearless concurrency owes a lot to standing on the shoulders of giants.
reply
I don't think fearless concurrency is strongly related to the atomic memory ordering model.

C++ 11 wants to model two non-trivial orderings, Acquire-Release and Consume-Release, but only Acquire-Release is actually usable. So yes, C and Rust both also choose to offer Acquire-Release ordering like C++ but it's also clear that the universe of possibilities is actually smaller than WG21 realised when C++ 11 was developed.

If on your first random stab you guess 65537 is prime, you're correct and you might foolishly assume all those Fermat Numbers are prime and then try say, 4294967297. Nope, that's divisible by 641 for some reason. There are only Five of these Fermat Primes so far as we know, they weren't common after all.

reply
C++11 borrowed heavily from the Java memory model that came before it.

It's basically everyone learning from everyone else on what works and what doesn't work.

reply
Nit: Rust currently adheres to C++20 memory model. At least one notable difference from earlier versions which tripped me up recently is that release sequences aren't extended by further writes.
reply
I’m not sure where you draw the conclusion that this was impactful. Intel had been sounding the alarm for at least a couple years at that point (longer if you were privy to private communication with them). Not to mention that C++ failed pretty miserably to enable safe concurrent and parallel programming for the masses, and the industry as a whole still struggles as a result.
reply
Languages have not yet evolved to multi-core properly IMO

I've sketched what I think an ideal "language of today" would look like, and one of the core tenets is making concurrent code explicit and part of the design surface.

You might imagine a webserver with main code like:

  spawn(on_core: 0) {
    await receive_packet(input)
  }
Or iterating over "num_cpus" and doing the same thing, with hashing based on CPU number for shared-memory resources.
reply
The problem is that when you build at this layer, you end up writing far more scheduling code than business logic. Eventually you've built the abstractions we already use today.

The vast, vast majority of software, and software developers, simply do not care about core affinity, NUMA, noisy neighbors, etc.

Even on systems or situations where this matters (massive number of cores and memory, multiple sockets, thousands of threads, etc.) the juice is rarely worth the squeeze.

Doubly so when you end up being I/O bound anyways.

Nearly every software developers wants their software to work across multiple deployment targets. Maybe you just target x86-64, and you don't care about the number of CPUs because you'll be upgrading your servers every couple of years.

No one wants to deal with difficult-to-reproduce issues related to scheduling in niche configurations. It's much more efficient for runtime or compiler engineers to solve these problems for us, leveraging the vast amount of resources of corporations to develop and test these features. Then you have a place your code can live happily ignorant of its runtime constraints.

And that, my friend, is the beauty of our industry.

reply
I disagree. In a world of different architectures, out-of-order execution, asymmetric cores, branch prediction, per-core caching and information leakage between threads and the branch not taken, my opinion is firmly that most people shouldn't be writing multithreaded code because it is almost impossible to get it correct and safe.

The effects can be subtle and lurk for years until a seemingly unrelated change exposes them. It starts with optimizations like instead of spawning a thread, you use a thread pool, possibly abstracted as some kind of executor service. Other code might use different thread pools to isolate them because each pool has its own limits on the number of threads. Some might grow but usually they won't.

Until one day you end up with thread starvation and a deadlock through a series of benign seeming optimizations. There are threads around but not in the right pools and the entire thing grinds to a halt.

Cooperative async/await on a single thread is the ideal solution for most use cases, particularly with complete environment teardown between requests. Yes, this is basically the PHP model.

reply
deleted
reply
That paper turned out to be enterily wrong, not only did single core performance keep increasing significantly, today most software is being written with almost no concurrency in mind
reply
That's because concurrency is really in the SDK or stdlib for the language or platform.

In my opinion, that's really the best place for it. From what I understand, that's always been FORTRAN's secret sauce.

I've been doing threaded code since Day One (Machine Code), and have gotten it wrong, quite often. There are a million things that can cause concurrent code to actually perform worse than single-thread code.

These days, I usually just let the platform pick the threads, and generally write code that will play along.

reply
The only software that is written without concurrency in mind is software that uses very few resources.

Anything else heavily uses concurrency in all kinds of ways.

reply