How to atomically update and share a vector of strings among threads?

I'm pretty sure that Arc will be involved somehow... but I just can't wrap my head around the best way to do this.
I want to construct a vector of strings at runtime and stuff (a pointer to?) that vector into a global variable, or, at least, into a variable that will be shared by dynamically created threads. Every once in a while, I will construct a new vector of strings and want to swap out, and discard, the old vector for the new one.

Or, should I use AtomicPtr for this?

As always, tips and pointers are gratefully accepted.

--wpd

You need this:

2 Likes

I tried this, but it behaves weird:

#![feature(once_cell)]

use std::lazy::SyncLazy;
use std::sync::{Arc, RwLock};

const GLOBAL_VEC: SyncLazy<RwLock<Arc<Vec<String>>>> = SyncLazy::new(||
    RwLock::new(Arc::new(Vec::new()))
);

fn main() {
    {
        let lazy = &GLOBAL_VEC;
        let lock = SyncLazy::force(lazy);
        let mut value = lock.write().unwrap();
        *value = Arc::new(vec!["Hello".to_string(), "World".to_string()]);
        println!("{value:?}");
    }
    {
        let lazy = &GLOBAL_VEC;
        let lock = SyncLazy::force(lazy);
        let value = lock.read().unwrap();
        println!("{value:?}"); // why does this read `[]`?
    }
}

(Playground)

Output:

["Hello", "World"]
[]

Version on Playground:

1.61.0-nightly (2022-03-19 8d60bf427a4b055f4641)

Can anyone tell me what I'm doing wrong? Maybe I misunderstand what the experimental std::lazy::SyncLazy does? See also Tracking Issue for once_cell.

This creates a new SyncLazy anywhere you use GLOBAL_VEC. You'd probably like to use static, not const.

1 Like

Then I'd probably do the following:

#![feature(once_cell)]

use std::lazy::SyncLazy;
use std::sync::{Arc, RwLock, RwLockReadGuard};

static GLOBAL_VEC: SyncLazy<RwLock<Arc<Vec<String>>>> = SyncLazy::new(||
    RwLock::new(Arc::new(Vec::new()))
);

fn set_vec(vec: Vec<String>) {
    *GLOBAL_VEC.write().unwrap() = Arc::new(vec)
}

fn get_vec_long_lasting() -> Arc<Vec<String>> {
    GLOBAL_VEC.read().unwrap().clone()
}

fn get_vec_quickly() -> RwLockReadGuard<'static, Arc<Vec<String>>> {
    GLOBAL_VEC.read().unwrap()
}

fn main() {
    set_vec(vec!["Hello".to_string(), "World".to_string()]);
    println!("{:?}", get_vec_quickly()); // this will block writing until return value is dropped
    println!("{:?}", get_vec_long_lasting()); // this will have to clone the Arc
}

(Playground)

Output:

["Hello", "World"]
["Hello", "World"]

Maybe it's not as efficient as arc-swap or some solution with AtomicPtr?

It should be noted that std::sync::RwLock's doc says:

The priority policy of the lock is dependent on the underlying operating system’s implementation, and this type does not guarantee that any particular policy will be used.

See also: How bad is the Potential deadlock mentioned in RwLock's document?

So maybe better use parking_lot, which seems to be better in many regards. parking_lot also does not need the .unwrap(). (Playground)

But like I said, maybe other solutions are more efficient anyway.