concurrency - What is the right way to write double-checked locking in Rust? -


i found this article, looks wrong because cell not guarantee synchronization between set() under lock , get() on lock.

does atomic_.store(true, ordering::release) affect other non-atomic write operations?

i tried write atomicptr looks close java-style failed. couldn't find examples of correct using of atomicptr in such cases.

does atomic_.store(true, ordering::release) affect other non-atomic write operations?

yes.

actually, primary reason ordering exists impose ordering guarantees on non-atomic reads , writes:

  • within same thread of execution, both compiler , cpu,
  • so other threads have guarantees in order in see changes.

relaxed

the less constraining ordering; operations cannot reordered operations on same atomic value:

atomic.set(4, ordering::relaxed); other = 8; println!("{}", atomic.get(ordering::relaxed)); 

is guaranteed print 4. if thread reads atomic 4, has no guarantee whether other 8 or not.

release/acquire

write , read barriers, respectively:

  • release used store operations, , guarantees prior writes executed,
  • acquire used load operations, , guarantees further reads see values @ least fresh ones written prior corresponding store.

so:

// thread 1 one = 1; atomic.set(true, ordering::release); 2 = 2;  // thread 2 while !atomic.get(ordering::acquire) {}  println!("{} {}", one, two); 

guarantees one 1, , says nothing two.

note relaxed store acquire load or release store relaxed load meaningless.

note rust provides acqrel: behaves release stores , acquire loads, don't have remember which... not recommend though, since guarantees provided different.

seqcst

the constraining ordering. guarantees ordering across threads @ once.


what right way write double-checked locking in rust?

so, double-checked locking taking advantage of atomic operations avoid locking when unnecessary.

the idea have 3 pieces:

  • a flag, false, , true once action has been executed,
  • a mutex, guarantee exclusion during initialization,
  • a value, initialized.

and use them such:

  • if flag true, value initialized,
  • otherwise, lock mutex,
  • if flag still false: initialize , set flag true,
  • release lock, value initialized.

the difficulty ensuring non-atomic reads/writes correctly ordered (and become visible in correct order). in theory, need full fences that; in practice following idioms of c11/c++11 memory models sufficient since compilers must make work.

let's examine code first (simplified):

struct lazy<t> {     initialized: atomicbool,     lock: mutex<()>,     value: unsafecell<option<t>>, }  impl<t> lazy<t> {     pub fn get_or_create<'a, f>(&'a self, f: f) -> &'a t             f: fnonce() -> t     {         if !self.initialized.load(ordering::acquire) { // (1)             let _lock = self.lock.lock().unwrap();              if !self.initialized.load(ordering::relaxed) { // (2)                 let value = unsafe { &mut *self.value.get() };                 *value = f(value);                 self.initialized.store(true, ordering::release); // (3)             }         }          *self.value.get()     } } 

there 3 atomic operations, numbered via comments. can check kind of guarantee on memory ordering each must provide correctness.

(1) if true, reference value returned, must reference valid memory. requires writes memory executed before atomic turns true, , reads of memory executed after true. (1) requires acquire , (3) requires release.

(2) on other hand has no such constraint because locking mutex equivalent full memory barrier: writes guaranteed have occured before , reads occur after. such, there no further guarantee needed load, relaxed optimized.

thus, far concerned, implementation of double-checking looks correct in practice.


for further reading, recommend the article preshing linked in piece linked. notably highlights difference between theory (fences) , practice (atomic loads/stores lowered fences).


Comments

Popular posts from this blog

android - InAppBilling registering BroadcastReceiver in AndroidManifest -

python Tkinter Capturing keyboard events save as one single string -

sql server - Why does Linq-to-SQL add unnecessary COUNT()? -