Страницы

Поиск по вопросам

пятница, 12 апреля 2019 г.

Rust. Проблема с многопоточностью

Пытаюсь освоить многопоточность в Rust. Прошу ответить на несколько вопросов. Читаю перевод Rustbook.
Мы оборачиваем данные в sync::Mutex, когда хотим использовать эти данные (эту переменную) в других потоках, чтобы не было "гонки данных"? То есть оборачиваем в Mutex, а там уже когда используем, то lock() и все остальные "становятся в очередь"? Я сейчас о примере с Rustbook:
use std::sync::{Arc, Mutex}; use std::thread; fn main() { let data = Arc::new(Mutex::new(vec![1u32, 2, 3])); for i in 0..3 { let data = data.clone(); thread::spawn(move || { let mut data = data.lock().unwrap(); data[i] += 1; }); } thread::sleep_ms(50); }
Какие-то странные и непонятные действия. Ну, хорошо, связали data с вектором, который обернут в Mutex, но зачем ещё в Arc? Внутри цикла зачем-то делаем копию и связываем с data, потом уже в замыкании опять новая переменная data. Объясните пожалуйста.
Моя попытка сделать десять потоков и в каждом из них прибавлять по единице к data. Неработающая попытка:
use std::thread; use std::sync::{Arc, Mutex};
fn main() { let mut data = Arc::new(Mutex::new(0));
let handles: Vec<_> = (0..10).map(|_| { thread::spawn(move|| { data.lock(); data += 1; //binary assignment operation `+=` cannot be applied to type `alloc::arc::Arc> }) }).collect();
for h in handles { h.join(); }
}
Почему опять ему (компилятору) не нравится?


Ответ

В книге очень подробно разобран этот пример с вариантами что будет без мьютексов и что будет без Arc:
Arc - является атомарным указателем со счетчиком ссылок. «Атомарный» означает, что им безопасно обмениваться между потоками. Чтобы гарантировать, что его можно безопасно использовать из нескольких потоков, Arc предполагает наличие еще одного свойства у вложенного типа. Он предполагает, что T реализует типаж Sync. В нашем случае мы также хотим, чтобы была возможность изменять вложенное значение. Нам нужен тип, который может обеспечить изменение своего содержимого лишь одним пользователем одновременно. Для этого мы можем использовать тип Mutex
Единственное что там вводит в заблуждение - используется затенение переменных, так что там три разных переменных с одинаковым именем data
Вот рабочий код вашего примера:
use std::thread; use std::sync::{Arc, Mutex};
fn main() { let data = Arc::new(Mutex::new(0));
let handles: Vec<_> = (0..10).map(|_| { let xd = data.clone();// (1) thread::spawn(move|| { let mut x = xd.lock().unwrap();// (2) *x+=1;// (3) }) }).collect();
for h in handles { h.join(); }
println!("{:?}",data) }
По пунктам что было не так:
Вы пытаетесь непосредственно обратиться к переменной data. Так как для типа Arc не реализован типаж неявного копирования Copy, то первый же поток захватит право владения переменной и она будет недоступна для остальных потоков (и для основного тоже). Поэтому используется явное клонирование указателя data.clone() Функция lock() возвращает специальный объект MutexGuard дополнительно завернутый в LockResult. Когда этот объект будет освобожден защелка будет отпущена. Поэтому результат возвращаемый lock() нужно сохранить в переменную. unwrap() вытаскивает MutexGuard из LockResult. К тому же этот объект нужен для доступа к самим данным. Для доступа к данным внутри MutexGuard нужно разыменовать то что мы получили в п.2.

Комментариев нет:

Отправить комментарий