2016-12-17 4 views
0

Я хочу, чтобы мой метод struct выполнялся синхронно. Я хотел бы сделать это с помощью Mutex (Playground): сообщениеКак избежать проблем с заимствованием мьютексов при использовании его защиты

use std::sync::Mutex; 
use std::collections::BTreeMap; 

pub struct A { 
    map: BTreeMap<String, String>, 
    mutex: Mutex<()>, 
} 

impl A { 
    pub fn new() -> A { 
     A { 
      map: BTreeMap::new(), 
      mutex: Mutex::new(()), 
     } 
    } 
} 

impl A { 
    fn synchronized_call(&mut self) { 
     let mutex_guard_res = self.mutex.try_lock(); 
     if mutex_guard_res.is_err() { 
      return 
     } 
     let mut _mutex_guard = mutex_guard_res.unwrap(); // safe because of check above 
     let mut lambda = |text: String| { 
      let _ = self.map.insert("hello".to_owned(), 
            "d".to_owned()); 
     }; 
     lambda("dd".to_owned()); 
    } 
}  

Ошибка:

error[E0500]: closure requires unique access to `self` but `self.mutex` is already borrowed 
    --> <anon>:23:26 
    | 
18 |   let mutex_guard_res = self.mutex.try_lock(); 
    |        ---------- borrow occurs here 
... 
23 |   let mut lambda = |text: String| { 
    |       ^^^^^^^^^^^^^^ closure construction occurs here 
24 |    if let Some(m) = self.map.get(&text) { 
    |        ---- borrow occurs due to use of `self` in closure 
... 
31 |  } 
    |  - borrow ends here 

Как я понимаю, когда мы заимствуем что-нибудь от структуры мы не можем использовать поля другие структуры в до нашего заема закончен. Но как я могу выполнить синхронизацию метода?

+0

Не могли бы вы привести [MCVE] пожалуйста? Это очень поможет! –

+0

@LukasKalbertodt Я уже делал это :) –

+0

Спасибо большое! Я немного отредактировал ваш ответ, надеюсь, вы не против. Я думаю, что в этой форме легче ответить. –

ответ

5

Закрытие требует изменчивой ссылки на self.map, чтобы вставить в нее что-то. Но захват закрытия работает только с целыми связями. Это означает, что если вы скажете self.map, замыкание пытается захватить self, а не self.map. И self не могут быть заимствованы/захвачены, потому что части self уже неизменно заимствованы.

Мы можем решить эту закрывающий захват проблемы путем введения новой привязки к карте в одиночку такой, что замыкание способно захватить его (Playground):

let mm = &mut self.map; 
let mut lambda = |text: String| { 
    let _ = mm.insert("hello".to_owned(), text); 
}; 
lambda("dd".to_owned()); 

Однако, есть то, что вы упустили: с synchronized_call() принимает &mut self, вам не нужны мьютексы! Зачем? Переменные ссылки также называются эксклюзивными ссылками, поскольку компилятор может обеспечить во время компиляции, что в любой момент времени существует только одна такая изменяемая ссылка.

Поэтому вы статически знаете, что существует не более одного экземпляра synchronized_call() работает на одном конкретном объекте в любой данный момент времени, если функция не является рекурсивной (называет себя).

Если у вас есть изменяемый доступ к мьютексу, вы знаете, что мьютекс разблокирован. См. the Mutex::get_mut() method for more explanation. Разве это не удивительно?

4

Мьютексы ржавчины не работают так, как вы пытаетесь их использовать. В Rust, мьютекс защищает конкретные данные, полагающиеся на механизм проверки заимствования, используемый в другом месте на языке. Как следствие, объявление поля Mutex<()> не имеет смысла, поскольку оно защищает доступ на чтение и запись к единичному объекту (), который не имеет значений для мутации.

Как объяснил Лукас, ваш объявленный call_synchronized не требует синхронизации, поскольку его подпись уже запрашивает эксклюзивную (изменяемую) ссылку на self, что предотвращает ее вызов из нескольких потоков на одном и том же объекте. Другими словами, вам необходимо изменить подписьcall_synchronized, потому что текущий не соответствует функциям, которые он предназначен для обеспечения.

call_synchronized необходимо принять общую ссылку на self, которая будет сигнализировать Rust о том, что она может быть вызвана из нескольких потоков в первую очередь.Внутри call_synchronized вызов Mutex::lock одновременно заблокировать мьютекс и обеспечить изменяемую ссылку на базовые данные, тщательно контекстными, так что блокировка удерживается на время обращения:

use std::sync::Mutex; 
use std::collections::BTreeMap; 

pub struct A { 
    synced_map: Mutex<BTreeMap<String, String>>, 
} 

impl A { 
    pub fn new() -> A { 
     A { 
      synced_map: Mutex::new(BTreeMap::new()), 
     } 
    } 
} 

impl A { 
    fn synchronized_call(&self) { 
     let mut map = self.synced_map.lock().unwrap(); 
     // omitting the lambda for brevity, but it would also work 
     // (as long as it refers to map rather than self.map) 
     map.insert("hello".to_owned(), "d".to_owned()); 
    } 
}