是否可以仅允许一个线程对共享数据进行突变?

时间:2019-03-06 00:33:27

标签: concurrency rust

是否有任何方法可以在线程之间(使用Arc共享数据,但仅允许单个线程能够对该数据进行突变?

在C语言中可能会发生这种情况,但是我看不到如何在Rust中做到这一点。

Arc<Mutex<T>>允许所有线程变异,而Arc<T>不允许所有线程变异。

2 个答案:

答案 0 :(得分:1)

您可以使用类型系统以禁止突变的方式包装Arc<Mutex<T>>,只有一个特权所有者除外。这是一个示例:

use std::sync::Arc;
use std::sync::Mutex;

pub struct Writer<T>(Arc<Mutex<T>>);

impl<T> Writer<T> {
    pub fn new(value: T) -> Self {
        Writer(Arc::new(Mutex::new(value)))
    }

    pub fn reader(&self) -> Reader<T> {
        Reader(Arc::clone(&self.0))
    }

    pub fn set(&self, value: T) {
        *self.0.lock().unwrap() = value;
    }

    pub fn get(&self) -> T
    where
        T: Clone,
    {
        self.0.lock().unwrap().clone()
    }
}

pub struct Reader<T>(Arc<Mutex<T>>);

// derive(Clone) uses incorrect bounds, so we must implement Clone manually
// (see https://stackoverflow.com/q/39415052/3650362)
impl<T> Clone for Reader<T> {
    fn clone(&self) -> Self {
        Reader(Arc::clone(&self.0))
    }
}

impl<T> Reader<T> {
    pub fn get(&self) -> T
    where
        T: Clone,
    {
        self.0.lock().unwrap().clone()
    }
}

如果将这段代码放在mod中,Rust的隐私控件将证明除通过以下方式之外,没有用户可以复制Writer或将Reader转换为Writer unsafe的使用。因此,您可以克隆Reader并将其发送到任意多个线程,而仅将Writer发送到应该具有写访问权的特定线程。

此设计有很多可能的变化;例如,您可以使用RwLock而不是Mutex来让多个读取者在不写入值的同时访问它。

Playground(基于Akiner Alkan的示例)

  

像C这样的事情是可能的

请注意,就像在Rust中一样,如果要在C语言中安全执行此操作,则需要某种同步(互斥或类似方法)。 Rust坚持要求您明确说明如何避免数据争用。 C的不同之处在于,它只会假设您知道自己在做什么,然后会因编写种族而粗暴地惩罚您。在Rust中,惯用的方法是使用标准库提供的安全抽象。但是,如果您有其他同步方法,并且可以证明Mutex是不必要的开销,则始终可以以C方式编写内容-原始指针在两个Rust中基本相同(在{{1 }}块和C。

答案 1 :(得分:0)

您可以在Arc<Mutex<T>>周围创建包装器,并通过设置器方法使用由弧的创建者提供的键来设置值。

use std::sync::Arc;
use std::sync::Mutex;
use std::thread;

#[derive(Clone)]
pub struct CustomArc<T> {
    mutable_arc: Arc<Mutex<T>>,
    mutator_key: String,
}

#[derive(Clone)]
struct MyStruct {
    inner_val: i32,
}

impl MyStruct {
    fn set_val(&mut self, val: i32) {
        self.inner_val = val;
    }

    fn get_val(&mut self) -> i32 {
        self.inner_val.clone()
    }
}

impl CustomArc<MyStruct> {
    fn new(val: MyStruct, mutator_key: String) -> CustomArc<MyStruct> {
        CustomArc {
            mutable_arc: Arc::new(Mutex::new(val)),
            mutator_key,
        }
    }

    fn set_inner_val(&mut self, value: i32, mutator_key: String) -> Result<(), SetError> {
        if mutator_key == self.mutator_key {
            self.mutable_arc.lock().unwrap().set_val(value);
            return Ok(());
        }

        Err(SetError::CouldNotSet)
    }

    fn get_inner_val(&self) -> i32 {
        self.mutable_arc.lock().unwrap().get_val()
    }
}

enum SetError {
    CouldNotSet,
}

fn main() {
    let my_struct = MyStruct { inner_val: 3 };

    let custom_arc = CustomArc::new(my_struct, "OwnerKey".to_string());
    let mut custom_arc1 = custom_arc.clone();
    let mut custom_arc2 = custom_arc.clone();
    let mut custom_arc3 = custom_arc.clone();

    thread::spawn(move || {
        println!(
            "Thread1 -> Current Value: {:?}",
            custom_arc1.get_inner_val()
        );
        if let Err(_err) = custom_arc1.set_inner_val(4, "AnotherKey".to_string()) {
            println!("Could not write in thread1");
        }
        println!("Thread1 -> Value: {:?}", custom_arc1.get_inner_val());
    });

    thread::sleep_ms(500);

    thread::spawn(move || {
        println!(
            "Thread2 -> Current Value: {:?}",
            custom_arc2.get_inner_val()
        );
        if let Err(_err) = custom_arc2.set_inner_val(5, "OwnerKey".to_string()) {
            println!("Could not write in thread2");
        }
        println!("Thread2 -> Value: {:?}", custom_arc2.get_inner_val());
    });

    thread::sleep_ms(500);

    thread::spawn(move || {
        println!(
            "Thread3 -> Current Value: {:?}",
            custom_arc3.get_inner_val()
        );
        if let Err(_err) = custom_arc3.set_inner_val(6, "SomeKey".to_string()) {
            println!("Could not write in thread3");
        }
        println!("Thread3 -> Value: {:?}", custom_arc3.get_inner_val());
    });

    thread::sleep_ms(500);
}

Playground

由于您的CustomArc是公开的,而mutable_arc字段是私有的,因此您应该通过板条箱外部的setter和getter访问它们。 mutator_key的所有者(可能还有其他线程)有权更改内部数据。