How can I lock the internals of my Rust data structure?

百般思念 提交于 2021-01-28 04:35:38

问题


I'm trying to implement a collection that stores values in both a vector and a hashmap and this is what I have so far:

pub struct CollectionWrapper {
    items:      Vec<Item>,
    items_map:  HashMap<ItemKey, Item>,
}

impl CollectionWrapper {
    pub fn new() -> Self {
        CollectionWrapper {
            items: Vec::new(),
            items_map: HashMap::new(),
        }
    }

    pub fn add(&mut self, item: Item) {
        let key = item.get_key();
        self.items.push(item.clone());
        self.items_map.insert(key, item.clone());
    }
}

I obviously need some kind of lock. I've looked at the Mutex Rust has, but I do not understand how to use it. When I search for the problem, I only find use cases where people spawn a bunch of threads and synchronize them. I'm looking for something like:

try {
    lock.lock();
    // insert into both collections
} finally {
    lock.unlock();
}

回答1:


I obviously need some kind of lock

I don't know that I agree with this need. I'd only introduce a lock when multiple threads could be modifying the object concurrently. Note that's two conditions: multiple threads AND concurrent modification.

If you only have one thread, then Rust's enforcement of a single mutable reference to an item will prevent any issues. Likewise, if you have multiple threads and fully transfer ownership of the item between them, you don't need any locking because only one thread can mutate it.

I'm looking for something like:

try {
    lock.lock();
    // insert into both collections
} finally {
    lock.unlock();
}

If you need something like that, then you can create a Mutex<()> — a mutex that locks the unit type, which takes no space:

use std::sync::Mutex;

struct Thing {
    lock: Mutex<()>,
    nums: Vec<i32>,
    names: Vec<String>,
}

impl Thing {
    fn new() -> Thing {
        Thing {
            lock: Mutex::new(()),
            nums: vec![],
            names: vec![],
        }
    }

    fn add(&mut self) {
        let _lock = self.lock.lock().unwrap();
        // Lock is held until the end of the block

        self.nums.push(42);
        self.names.push("The answer".to_string());
    }
}

fn main() {
    let mut thing = Thing::new();
    thing.add();
}

Note that there is no explicit unlock required. When you call lock, you get back a MutexGuard. This type implements Drop, which allows for code to be run when it goes out of scope. In this case, the lock will be automatically released. This is commonly called Resource Acquisition Is Initialization (RAII).

I wouldn't recommend this practice in most cases. It's generally better to wrap the item that you want to lock. This enforces that access to the item can only happen when the lock is locked:

use std::sync::Mutex;

struct Thing {
    nums: Vec<i32>,
    names: Vec<String>,
}

impl Thing {
    fn new() -> Thing {
        Thing {
            nums: vec![],
            names: vec![],
        }
    }

    fn add(&mut self) {
        self.nums.push(42);
        self.names.push("The answer".to_string());
    }
}

fn main() {
    let thing = Thing::new();
    let protected = Mutex::new(thing);
    let mut locked_thing = protected.lock().unwrap();
    locked_thing.add();
}

Note that the MutexGuard also implements Deref and DerefMut, which allow it to "look" like the locked type.



来源:https://stackoverflow.com/questions/36039917/how-can-i-lock-the-internals-of-my-rust-data-structure

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!