Перейти к содержанию

Box, Rc, Arc и связь с потоками

Posted on:19 апреля 2023 г. at 08:46

Example Dynamic OG Image link

Поток

Работа с потоками на традиционных языках программирования требует некоторой заботы об общей памяти. Проблема может возникнуть, когда один или несколько потоков одновременно изменяют общие данные, вызывая ошибку.

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

Прежде чем перейти к основной теме статьи, необходимо просмотреть некоторые темы, чтобы постепенно преуменьшать содержание.

Что такое Box<T>?

Box - это, по сути, тип не копируемого указателя с определенным размером в стеке с типом дженерика T в куче. Box можно использовать для:

Компилятор Rust должен знать размер объектов во время компиляции, поэтому определение структуры с переменным размером может быть проблемой для компилятора. Чтобы решить это, необходимо обернуть проблемный тип в поле, которое имеет фиксированный тип в стеке, оно будет указывать на кучу, так как данные могут свободно расти.

Стек в Rust имеет ограничение по объему памяти в 2 МБ (по умолчанию) и заполняется во время компиляции.

В этом примере Block инкапсулирует другие Block внутри себя, что делает невозможным определение общего размера блокчейна. Это связано с тем, что блокчейны могут содержать “n” блоков.

enum Blockchain {
    Block(u32, Box<Blockchain>),
    End
}

use Blockchain::{Block, End};

fn main() {
    let blockchain = Block(0,
	    Box::new(Block(1,
	    Box::new(Block(2,
	    Box::new(End))))));
}

Что такое Rc<T>?

Rc расшифровывается как подсчет ссылок, и он используется в Rust для того, чтобы сделать возможным, что одно значение имеет несколько владельцев. Поведение Rc, как следует из названия, отслеживает количество ссылок на значение, что определяет, используется ли значение по-прежнему. В случае, если номер ссылки равен нулю, значение может быть произвольным.

Важно сказать, что Rc - однопоточный.

Этот пример похож на предыдущий, однако в этом примере основной блокчейн передает свое право собственности другим.

use std::rc::Rc;

enum Blockchain {
    Block(u32, Rc<Blockchain>),
    End
}

use Blockchain::{Block, End};

fn main() {
    let blockchain_main = Rc::new(Block(0,
	    Rc::new(Block(1,
	    Rc::new(Block(2,
	    Rc::new(End)))))));

    let blockchain_a = Block(10, Rc::clone(&blockchain_main));
    let blockchain_b = Block(100, Rc::clone(&blockchain_main));
}

Что такое Arc<T>?

Arc - это тип из подмодуля std::sync, и с его помощью можно обрабатывать совместно изменяемое состояние с помощью потоков. Arc расшифровывается как подсчет атомарных ссылок, это похоже на Rc, подсчет ссылок, который мы видели ранее, но с возможностью работы с потоками.

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

use std::thread;
use std::sync::Arc;
enum Blockchain {
    Block(u32, Arc<Blockchain>),
    End
}

use Blockchain::{Block, End};

fn main() {
    let blockchain = Arc::new(
        Block(0,
            Arc::new(Block(1,
            Arc::new(Block(2,
            Arc::new(End)))))
    ));

    for t in 3..10 {
        let clone = blockchain.clone();
        thread::spawn(move || {
            let _blockchain_thread = Block(t, clone);
        }).join().unwrap();
    }
}

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