3 ms·
Yes, double-checked locking is broken if the code does not use fences to enforce the order of checking the initialized flag and getting the value. TFA uses atom
by Arnavion 3y ago
Yes, double-checked locking is broken if the code does not use fences to enforce the order of checking the initialized flag and getting the value. TFA uses atomic operations to do that.
- Groxx 3y agoTo spell it out a bit more for other readers, this article is doing effectively this: get() { if needsinit_or_wait_if_initting() { // lock (atomics) then check thing = init_thing() } return thing } Which is fine because the check always occurs before reads or writes. The classic double-checked lock mistake looks something like this, when following this example: get() { // "fast" path: get thing if it exists, because // the code looks like that means it's done, right? // no check because checks are slow and we move fast! if thing != nil { // first check return thing } // slow path: use slow stuff which is slow :( if needsinit_or_wait_if_initting() { // lock then second check thing = init_thing() } return thing } It's that un-guarded see-if-nil-and-return that makes it unsafe, because the assignment is not guaranteed to be "complete" in many cases. There is no explicit relationship between the initialization, assignment, and read, so the compiler can do stuff that doesn't match what the code looks like, e.g. reorder "save the new memory address to `thing`" before "init the thing" (so you temporarily have an uninitialized non-nil object), and at runtime you can get stuff like "only the first half of the memory synced to that CPU core so you got a busted object".
- gpderetta 3y agoThe double checked locking is perfectly fine as long as the fast path 'thing' access has the right semantics (in C and C++ you need an atomic load with consume ordering).