Перейти к основному содержимому
  1. Rust/

Arc в Rust: потокобезопасное разделённое владение

574 слов·3 минут· loading · loading · ·
Rust Dev
about-rust - Эта статья часть цикла.
Часть 17: Эта статья

Введение
#

Если вы уже знакомы с Rc<T> (если не знакомы, то можете прочитать прошлую статью), то, возможно задавались вопросом - а можно ли безопасно делить данные между потоками? Ответ — можно, но для этого нужен другой тип: Arc<T>.

Что такое Arc?
#

Arc расшифровывается как Atomically Reference Counted (атомарный счетчик ссылок). Он работает как Rc, но с одной важной особенностью: он потокобезопасен. То есть, в отличие от Rc, его можно использовать в многопоточном коде.

Синтаксис базового использования:
#

use std::sync::Arc;

let data = Arc::new(vec![1, 2, 3]);
let data_cloned = Arc::clone(&data);

Ключевые особенности Arc:
#

  • Подсчёт ссылок происходит атомарно (через атомарные инструкции процессора).
  • Может использоваться в нескольких потоках.
  • Не даёт изменять данные напрямую — для этого используется Mutex, RwLock или другие инструменты о которых постараюсь рассказать в следующих статьях.

Сравнение с Rc:
#

ТипПоддерживает многопоточностьАтомарный подсчет
Rc❌ Нет❌ Нет
Arc✅ Да✅ Да

Пример
#

Пример проблемы с Rc в многопоточном коде
Шаг 1/2
 1use std::rc::Rc;
 2use std::thread;
 3
 4fn main() {
 5    let data = Rc::new(vec![1, 2, 3]);
 6
 7    let handle = thread::spawn(move || {
 8        println!("{:?}", data);
 9    });
10
11    handle.join().unwrap();
12}

1. Этот код не скомпилируется.

  • Ошибка: Rc<Vec<i32>> cannot be sent between threads safely

Пример для случая когда нужно изменить данные
#

Arc сам по себе не даёт изменять содержимое.

Если нужно менять данные из разных потоков, используйте Mutex (мютекс это как замок, который позволяет работать с данными только одному потоку за раз):

Пример с изменением данных
 1use std::sync::{Arc, Mutex};
 2use std::thread;
 3
 4fn main() {
 5    let counter = Arc::new(Mutex::new(0));
 6    let mut handles = vec![];
 7
 8    for _ in 0..10 {
 9        let counter_cloned = Arc::clone(&counter);
10        let handle = thread::spawn(move || {
11            let mut num = counter_cloned.lock().unwrap();
12            *num += 1;
13        });
14        handles.push(handle);
15    }
16
17    for handle in handles {
18        handle.join().unwrap();
19    }
20
21    println!("Result: {}", *counter.lock().unwrap());
22}

Разберем подробнее что же тут происходит. Это пример демонстрации безопасного изменения общего значения из нескольких потоков:

  1. counter — это общая переменная, защищённая Mutex и обёрнутая в Arc (как мы уже знаем он позволяет безопасно делиться данными между потоками).
  2. В цикле создаётся 10 потоков с thread::spawn каждый из которых:
  • получает свою копию Arc для counter
  • блокирует Mutex для получения доступа к данным
  • увеличивает значение на 1
  1. handle.join() ждёт завершения всех потоков.

Мы изучим Mutex и thread::spawn более глубоко в следующих статьях - это важные концепции языка Rust для работы с потоками.

Практические советы по Arc
#

  • Используйте Arc если данные нужны в нескольких потоках.
  • Для изменяемых данных — комбинируйте с Mutex, RwLock и т.д.
  • Не злоупотребляйте Arc: каждый клон — это атомарная операция, она дороже, чем обычный счётчик Rc.
  • 🦀 hard skill не для начинающих!
    По умолчанию Arc<T> — это Send + Sync для случая когда T: Send + Sync.

Заключение
#

Arc полезен когда вы работаете с многопоточностью. Он гарантирует безопасность при доступе к общим данным. Но требует осторожности в плане производительности. Если вам не нужен доступ из разных потоков - используйте Rc. Но когда дело доходит до многопоточного доступа — Arc становится незаменимым.

На сегодня это все!

about-rust - Эта статья часть цикла.
Часть 17: Эта статья

Связанные статьи

Rc в Rust: Руководство по разделённому владению и RefCell
771 слово·4 минут· loading · loading
Rust Dev
Заимствование (Borrowing) в Rust
796 слов·4 минут· loading · loading
Rust Dev
Владение (Ownership) в Rust
772 слов·4 минут· loading · loading
Rust Dev