2017-01-20 2 views
0

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

компилятор отвергает меня передавая внутренний член структуры в Arc::new():

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

struct Worker { 
    name: String, 
} 

struct Master { 
    workers: Vec<Worker>, 
} 

impl Worker { 
    fn start(&self) { 
     println!("My name is {} and I'm working!", self.name); 
     thread::sleep_ms(100_000); 
    } 
} 

impl Master { 
    pub fn run_test(&mut self) { 
     for i in 0..10 { 
      self.workers.push(Worker { 
       name: String::new() + "Worker" + &i.to_string() 
      }); 
     } 
     let mut data = Arc::new(self.workers); 

     for i in 0..10 { 
      let local_data = data.clone(); 
      thread::spawn(move || { 
       local_data[i].start(); 
      }); 
     } 

     thread::sleep_ms(100_000); 
    } 
} 


fn main() { 
    let mut master = Master { workers: vec![] }; 
} 

Сообщение об ошибке:

error[E0507]: cannot move out of borrowed content 
    --> <anon>:26:33 
    | 
26 |   let mut data = Arc::new(self.workers); 
    |         ^^^^ cannot move out of borrowed content 

Что я делаю неправильно? Это идиоматическая ржавчина?

+3

Что именно вы хотите с этим «дугой»? «Рабочий вектор»? 'Arc' нуждается в собственности того, с чем он управляет, но вы не можете предоставить право собственности, потому что у вас есть только заимствование вектора. Есть много вопросов с сообщением об ошибке «не может выйти из заимствованного контента»] (http://stackoverflow.com/search?q=%5Brust%5D+%22cannot+move+out+of+borrowed+content%22) уже. Может быть, один из них может вам помочь? –

+3

Также: интерполяция строк в Rust обычно не выполняется с помощью '+' оператора, а с 'format!()'. Вы можете заменить свою странную строку 'name: format! (« Worker {} ", i)'. Это делает то же самое, выглядит более чистым и позволяет избежать ненужных распределений кучи. –

+0

Я действительно пытаюсь управлять рабочим вектором. Моя идея заключалась в том, что каждый поток будет рабочим и будет содержать структуру рабочего, поскольку это внутреннее состояние, в то время как мастер сохраняет ссылку на вектор работников. – Dash83

ответ

6

Добро пожаловать в собственность.

В ржавчине любой отдельный фрагмент данных имеет один и ровно один владелец. Не обманывайтесь Rc и Arc: они представляют собой общий интерфейс поверх одного (невидимого) владельца.

Самый простой способ выражения собственности является значением:

struct Master { 
    workers: Vec<Worker> 
} 

Здесь Master владеет Vec<Worker> который сам владеет мультипликатор Worker.

Аналогично, функции, которые принимают свой аргумент по значению (например, fn new(t: T) -> Arc<T>), получают право собственности на свой аргумент.

И это где проблема лежит:

Arc::new(self.workers) 

означает, что вы, в то же время:

  • утверждающей, что Master является владельцем workers
  • утверждают, что Arc является владелец workers

Учитывая правило один и ровно один владелец, это явно трудноизвлекаемо.


Итак, как вы обманываете и имеете нескольких совладельцев для одного фрагмента данных?

Хорошо ... используйте Rc или Arc!

struct Master { 
    workers: Arc<Vec<Worker>> 
} 

И теперь создание data так просто, как:

let data = self.workers.clone(); 

, который создает новый Arc (который только врезается счетчик ссылок).


Это еще не все.Основным принципом системы заимствований является: Смещение XOR Mutability.

С Arc касается сглаживания, он предотвращает изменчивость. Вы не можете вставлять работников в self.workers больше!

Есть несколько решений, таких как отложив инициализацию self.workers, пока вектор не будет построен, однако наиболее распространенным является использование клеток или мьютексы, то есть Rc<RefCell<T>> или Arc<Mutex<T>> (или Arc<RwLock<T>>).

RefCell и Mutex - это обертки, которые перемещают проверку заимствования из времени компиляции во время выполнения. Это дает немного большую гибкость, но может привести к панике во время выполнения вместо ошибок времени компиляции, поэтому лучше всего использовать в качестве последнего средства.

+0

Спасибо, @Matthieu, проницательные ответы, как всегда. Тем не менее, я сейчас в тупике. По правде говоря, мне не нужна эта общая коллекция работников и начинать темы для каждого из них, я мог бы создать отдельный рабочий двоичный файл, который принимает параметры для запуска, а затем запускает процессы и забывает о собственности, но так как Rust способствует бесстрашному параллелизм, я решил сделать это выстрелом. Однако перемещение проблемы из компилятора во время выполнения не кажется бесстрашным. Есть ли способ добиться желаемого эффекта при проверке компилятора? Или я просто смотрю на это неправильно? – Dash83

+0

@ Dash83: Бесстрашный означает, что язык имеет свою спину (во время компиляции или времени выполнения), хотя я тоже предпочитаю проверять время компиляции, а не паниковать двоичные файлы. Поскольку вы не изменяете «рабочие» в потоке, вы можете использовать ящик, подобный «crossbeam», который поддерживает ссылку на данные стека из нескольких потоков, и вам больше не понадобится «Arc». Другое решение - скопировать «Рабочий». Еще один заключается в том, чтобы «Мастер» отказался от собственности «Работник» ('for w in self.workers.drain() {...}' ... это действительно зависит от ваших ограничений :) –

+0

вы, сэр, являетесь ученым , Области поперечного сечения отлично подходят для этого первоначального доказательства концепции. Не уверен, что буду придерживаться этого ящика в долгосрочной перспективе, так как я предпочел бы изучить лучшие практики параллелизма в Rust и создать хороший код вокруг них, но он работает сейчас! Благодаря! – Dash83