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.
It's basically everyone learning from everyone else on what works and what doesn't work.
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.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.
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.
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.
Anything else heavily uses concurrency in all kinds of ways.