Since then, two important things happened.
1. On Windows 8 and beyond Rust moved to WaitOnAddress with an API similar to the futex on several other systems.
2. We found out SRWLocks have a significant (arguably fatal, but depending on your use case it may seem irrelevant) difference between how they actually work and what Microsoft's API said about them. This bug is fixed... in Microsoft's own version control, not in released Windows versions.
Specifically SRWLocks may silently give you a Write Lock, even if you asked only for a Read Lock, in the case where the lock was just released at the moment you asked. If you were expecting other threads to also get a read lock, which would ordinarily be possible - too bad, you've secretly been given the exclusive write lock so read locks are unavailable until you release it.
The actual reason seems to be this: SRWLocks are small (a single pointer, with some low bits stolen to hide metadata) and the authors forgot that they actually know (because it's a different function call) whether you asked for a read or a write lock. Since they didn't have anywhere to store this single bit (read or write) they just assumed they don't know in this edge case where the lock happens to be available immediately, and since they "don't know" they always give you a write lock anyway. Oops.
[Edited to make minor clarifications]
Oh hey, I reported that bug and behavior! One of my proudest reports. Not too often you find a legit bug, or at least documentation oversight, in such a core API.
It's definitely an actual bug not a doc bug. I've explained this a few times to people and it does seem like the natural inclination is to assume SRWLock must be supposed to do that, but I was glad to see internally Microsoft did fix this, because it's clearly the Wrong Thing™.
I see two common defences for this bug. One is "Actually, it's supposed to be unfair, you don't understand why that's a good idea". Which assumes I'm expecting a fair lock and I'm unhappy not to get it. Giving a reader the reader lock when there's a writer waiting would be unfair - and probably a bad idea but I'm open to it if somebody presents benchmarks - but giving them the writer lock is just a bug.
Another is "Actually, there is a writer waiting, and this way that writer gets the lock faster". As you wrote the example code you know that's false, there is no writer waiting, there are only readers and (in the buggy scenario) they're blocked forever for no reason.
I definitely spent a lot of time carefully crafting the message and iterating on a minimal repro.
Writing on the internet requires a LOT of defensive effort. It’s very annoying but is what it is. I write blog posts and my secret goal is “high views, low comments”. Because most comments are “well ackchyually“.
For the Reddit thread it helped that I tagged u/STL and he responded quickly in agreement. That was very intentional on my part! I’m still a little sad I didn’t get a Raymond Chen comment though!
One of the smallest pieces of work I'm proud of is a tool that automates the labor of Raymond's "The poor man's way of identifying memory leaks". The part where you need to be familiar with how your data types look in memory isn't automated, that's on you, but my tool (leakdice because it replaces the hexadecimal dice I previously used for this in real life) picks a random page of heap in a chosen (Linux) process and shows you what's in it, the rest is up to you as Raymond explains.
Not to my knowledge. But I kinda called him out for having a potentially false blog post so I was hoping he'd do a follow-up!
Here's STL's (nominative determinism at work) GitHub issue for Microsoft's C++ stdlib implementation about this https://github.com/microsoft/STL/issues/4448
Here's the C++ Reddit thread where the bug was shown: https://www.reddit.com/r/cpp/comments/1b55686/maybe_possible...
Here's the Rust change which was merged for 1.78: https://github.com/rust-lang/rust/pull/121956/
A classic example is a set of bank accounts, atomically transacting with each other. Fine-grained per-account locking is possible, but risks deadlock due to lock ordering inversion. A simple solution is to replace per-account locks with a singleton global lock, covering all accounts. Any transaction must first acquire this lock, and now deadlock is impossible.
But this is an awkward fit for Rust, whose locks want to own the data they protect. What's the best way to express a global lock, enforcing that certain data may only be accessed while the global lock is held?
One piece of advice I'd suggest is, write APIs which take that MyTransferToken to signify that you must take the lock before calling them, it can be a unit type (a Zero Size type, a struct with no members) at the start if you like, but I suspect you'll find that across several functions which take that MyTransferToken you realise actually the data I was going to put in a separate parameter is really always accompanying that token, and so it might as well go inside the the MyTransferToken and before you know it your unit type that was just to ensure correct locking is in fact an object with important data protected by the lock.
Mutex<HashMap<CustomerID, Account>>
Instead of HashMap<CustomerID, Mutex<Account>>
Using a singleton is kind of overkill.But in the real world you'd probably use a transactional database for this (there are a few in memory options) with retries.
[1]: https://doc.rust-lang.org/std/sync/struct.LazyLock.html [2]: https://doc.rust-lang.org/std/sync/struct.OnceLock.html [3]: https://crates.io/crates/once_cell
When you lock, the returned MutexGuard is owned, so you can pass it around or return it and it’s only dropped when it ultimately goes out of scope.
One thing I found lacking in the book were the examples. It has tons, but all of them are extremely focused on the topic they are illustrating and most feel very contrived. Would anyone here have a suggestion for a small/medium-sized project (weekend sized) which would actually use the patterns discussed in the book?
My #1 actually isn't a book at all, but the two "atomic Weapons" talks by Herb Sutter, which are extremely good. https://youtu.be/A8eCGOqgvH4 https://youtu.be/KeLBd2EJLOU
Most of the content is not too specific to Linux, much like this book is not too specific to Rust.
The only places I know where it isn’t applicable are the Linux kernel and Java, because their memory models and concurrency primitives predate and significantly differ from the Rust/C++/C models.
For the Linux memory model, there is Paul McKenney’s free book, “is parallel programming hard, and if so, what can you do about it?” https://cdn.kernel.org/pub/linux/kernel/people/paulmck/perfb...
Make sure you get the second edition, it is updated for C++17.
https://www.amazon.com/C-Concurrency-Action-Anthony-Williams...
For many languages there is nothing resembling this, they tend to not get into the details Mara covers, if you get a mutex and maybe atomic arithmetic then they're done.
If you wondered about C or C++, this book is the same content as for those languages but with Rust's syntax. The discrepancy between Rust's memory model and the memory model adopted in C++ 11 and subsequently C is mostly about a feature that's not available in your C or C++ compiler and (which is why Rust doesn't have it) probably won't ever be.
C++ x.store(r1, std::memory_order_relaxed);
is literally the same thing as
Rust x.store(r1, std::sync::atomic::Ordering::Relaxed);
The biggest syntax difference is that C++ x.store(r1) compiles, and in Rust it doesn't. But, chances are after reading Mara's book you will think it's weird not to specify the Ordering needed and never use this uh, convenience.
Java's happens-before memory model is similar to C++'s.
I'll prob get this book, if only for the memory model chapter.
Where do Rust and C++ lie wrt C# and Java?
The original Java 5 memory model only offered sequentially-consistent atomics to establish cross-thread happens-before in a primitive way. The C++11 memory model added three more kinds of atomics: acquire/release, consume/release (which was essentially a mistake [1]), and relaxed atomics (which, to oversimplify, establish atomicity without happens-before). Pretty much every memory model since C++11--which includes the Rust memory model--has based its definition on that memory model, with most systems defaulting an otherwise unadorned atomic operation to sequentially-consistent. Even Java has retrofitted ways to get weaker atomic semantics [2].
As a practical matter, most atomics could probably safely default to acquire/release over fully sequentially-consistent. The main difference between the two is that sequentially-consistent is safer if you've got multiple atomic variables in play (e.g., you're going with some fancy lockless algorithm), whereas acquire/release tends to largely be safe if there's only one atomic variable of concern (e.g., you're implementing locks of some kind).
[1] A consume operation is an acquire, but only for loads data-dependent on the load operation. This is supposed to represent a situation that requires no fences on any system not named Alpha, but it turns out for reasons™ that compilers cannot reliably preserve source-level data dependencies, so no compiler really implemented consume/release.
[2] Even Java 5 may have had it in sun.misc.Unsafe; I was never familiar with that API, so I don't know for certain.
* while it tells you how to do lock-free programming but doesn't teach you why, nor whether you should it.
* it has a relatively narrow focus on linearizability, but the truth is memory is neither linearizable nor sequentially consistent. These days it is agreed that Lamport's "happens before" relationship and acquire-release are a better way to reason on multithreaded code.
https://docs.oracle.com/javase%2F8%2Fdocs%2Fapi%2F%2F/java/u...
If you want to write a HTTP server, people are guided towards Axum/Tokio, and thus async rust.
If you want to use async Rust, read this book?
This books covers assembly level atomics, and creating your own channels, in beginner chapters.
Is that necessary for writing a HTTP server in Rust?
From the topics in the TOC, this book is useful of you want you write concurrency primitives. I wouldn't recommend it if you just want to _use_ Arc/Mutex/crossbeam-channel.
Usually when you have a textbook, they will have some nice illustration that is tangentially related to the content of the book (like fibonacci spiral for a math book or some chemical reaction for a chemistry book for example). But I suppose that there isn't really such an equivalent unless it's a computer graphics book.
I guess it's also like how every project has to have its own "cutesey" mascot.
https://www.oreilly.com/content/a-short-history-of-the-oreil...
TL;DR:
> Some of the people at O’Reilly were taken aback: they thought the animals were weird, ugly, and a bit scary. But Tim [O'Reilly] got it immediately—he liked the quirkiness of the animals, thought it would help to make the books stand out from other publishers’ offerings—and it just felt right.
They even have a browser which helps you identify the animal:
/s
I think it's just an O'Reilly thing. It keeps the people guessing.
Actually, the covers also make the books easily recognizable. Animals, statues, etc., are all good memory association drivers.