Можно ли разрешить только одному потоку изменять общие данные? - PullRequest
1 голос
/ 06 марта 2019

Есть ли способ обмена данными между потоками (с Arc), но только один поток может изменять эти данные?

Нечто подобное было бы возможно в C, но я не понимаю, как это сделать в Rust.

Arc<Mutex<T>> позволяет изменять все потоки, в то время как Arc<T> не разрешает ничего.

Ответы [ 2 ]

1 голос
/ 10 марта 2019

Вы можете использовать систему типов, чтобы обернуть 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 s в любое количество потоков, но отправлять Writer только в конкретный поток, который должен иметь доступ для записи.

Существует множество возможных вариантов этого дизайна;например, вы можете использовать RwLock вместо Mutex, чтобы позволить нескольким читателям одновременно получать доступ к значению, пока оно не записывается.

Playground (на примере Akiner Alkan)

Нечто подобное можно было бы сказать, например, C

Обратите внимание, что, как и в Rust, если вы хотите сделать безопасно в C, вынужна какая-то синхронизация (мьютекс или аналогичный).Rust настаивает на том, чтобы вы четко указывали, как избежать гонок данных.С отличается тем, что он просто предполагает, что вы знаете, что вы делаете, а затем жестоко накажет вас за написание гонок.В Rust идиоматический подход заключается в использовании безопасных абстракций, предоставляемых стандартной библиотекой.Однако, если у вас есть какие-то другие средства синхронизации и вы можете доказать, что Mutex - ненужные накладные расходы, вы всегда можете просто написать что-то по-C - необработанные указатели по сути одинаковы в обоих Rust (в пределах блока unsafe)и С.

0 голосов
/ 06 марта 2019

Вы можете создать обертку вокруг 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);
}

Детская площадка

Поскольку ваш CustomArc является открытым, а поле mutable_arc - закрытым, вы должны получать к ним доступ через сеттер и геттеры вне ящика. Владелец (возможно, другие потоки) mutator_key имеет право изменять внутренние данные.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...