Подтвердить что ты не робот

Что происходит, когда Arc клонируется?

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

use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

fn main() {
    let data = Arc::new(Mutex::new(vec![1, 2, 3]));

    for i in 0..3 {
        let data = data.clone();
        thread::spawn(move || {
            let mut data = data.lock().unwrap();
            data[0] += i;
        });
    }

    thread::sleep(Duration::from_millis(50));
}

Что происходит на линии let data = data.clone()?

В книге Руста

мы используем clone() для создания нового дескриптора. Затем этот дескриптор перемещается в новый поток.

Что такое новый "владеющий дескриптор"? Это похоже на ссылку на данные?

Так как clone принимает &self и возвращает a Self, каждый поток изменяет исходные данные вместо копии? Я думаю, поэтому код не использует data.copy(), но data.clone() здесь.

data с правой стороны является ссылкой, а data слева является принадлежащим ему значением. Здесь есть переменная тень.

4b9b3361

Ответ 1

[...] что происходит на let data = data.clone()?

Arc обозначает A текст R C. Arc управляет одним объектом (типа T) и служит прокси-сервером для разрешения совместного использования, что означает: один объект принадлежит нескольким именам. Вау, это звучит абстрактно, пусть сломает его!

Совместное владение

Скажем, у вас есть объект типа Turtle 🐢, который вы купили для своей семьи. Теперь возникает проблема, что вы не можете назначить четкого владельца черепахи: каждый член семьи является владельцем этого питомца! Это означает (и извините за то, что здесь было болезненно), что если один член семьи умрет, черепаха не умрет вместе с этим членом семьи. Черепаха будет умирать, только если все члены семьи уйдут. Каждый человек владеет, а последний очищает.

Итак, как бы вы выразили такую ​​совместную собственность в Rust? Вы быстро заметите, что это невозможно сделать только с помощью стандартных методов: вам всегда нужно выбирать одного владельца, а все остальные будут иметь ссылку на черепаху. Нехорошо!

Итак, придем Rc и Arc (что для этой истории служит той же цели). Они позволяют использовать совместное владение, слегка перебирая небезопасную-ржавчину. Посмотрите на память после выполнения следующего кода (обратите внимание: макет памяти предназначен для обучения и может не представлять собой то же самое расположение памяти из реального мира):

let annas = Rc::new(Turtle { legs: 4 });

Память:

  Stack                    Heap
  -----                    ----


  annas:
+--------+               +------------+
| ptr: o-|-------------->| count: 1   |
+--------+               | data: 🐢   |
                         +------------+

Мы видим, что черепаха живет в куче... рядом с счетчиком, который установлен в 1. Этот счетчик знает, сколько владельцев объекта data в настоящее время. И 1 правильно: annas - единственный, владеющий черепахой прямо сейчас. Пусть clone() Rc, чтобы получить больше владельцев:

let peters = annas.clone();
let bobs = annas.clone();

Теперь память выглядит так:

  Stack                    Heap
  -----                    ----


  annas:
+--------+               +------------+
| ptr: o-|-------------->| count: 3   |
+--------+    ^          | data: 🐢   |
              |          +------------+
 peters:      |
+--------+    |
| ptr: o-|----+
+--------+    ^
              |
  bobs:       |
+--------+    |
| ptr: o-|----+
+--------+

Как вы можете видеть, черепаха все еще существует только один раз. Но счетчик ссылок был увеличен и теперь составляет 3, что имеет смысл, потому что у черепахи сейчас три владельца. Все эти три владельца ссылаются на этот блок памяти на кучу. Это то, что книга Rust называет принадлежащим дескриптору: каждый владелец такого дескриптора также имеет вид собственного объекта.

(также см. "Почему std::rc::Rc<> не копировать?" )

Атомарность и мутность

Какую разницу между Arc<T> и Rc<T> вы спрашиваете? Arc увеличивает и уменьшает счетчик в атомном режиме. Это означает, что несколько потоков могут увеличивать и уменьшать счетчик одновременно без проблем. Вот почему вы можете отправлять Arc через границы потоков, но не Rc s.

Теперь вы заметили, что не можете мутировать данные через Arc<T>! Что делать, если ваш 🐢 теряет ногу? Arc не предназначен для разрешенного доступа нескольких владельцев (возможно) в одно и то же время. Вот почему вы часто видите такие типы, как Arc<Mutex<T>>. Mutex<T> - это тип, который предлагает внутреннюю изменчивость, что означает, что вы можете получить &mut T от &Mutex<T>! Это обычно противоречит основным принципам Rust, но совершенно безопасно, поскольку мьютекс также управляет доступом: вы должны запрашивать доступ к объекту. Если другой поток/источник в настоящее время имеет доступ к объекту, вам нужно подождать. Поэтому в один данный момент времени существует только один поток, доступ к которому можно получить T.

Заключение

[...] - каждый поток изменяет исходные данные вместо копии?

Как вы можете, надеюсь, понять из приведенного выше объяснения: да, каждый поток изменяет исходные данные. A clone() на Arc<T> не будет клонировать T, а просто создает другой принадлежащий ему дескриптор; который, в свою очередь, является просто указателем, который ведет себя так, как будто он владеет базовым объектом.

Ответ 2

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

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

Arc - один из стандартных типов библиотек, который обходит внутреннюю систему собственности. Он по существу управляет указателем сам по себе, и вызов clone() возвращает новый Arc, который указывает на ту же самую часть памяти, которую сделал оригинал.. с увеличенным счетчиком ссылок.

Таким образом, на высоком уровне да, clone() возвращает новый экземпляр Arc, и право собственности на этот новый экземпляр перемещается в левую часть задания. Однако внутренне новый экземпляр Arc по-прежнему указывает, где старый сделал.. через необработанный указатель (или, как он появляется в источнике, через экземпляр Shared, который является оберткой вокруг необработанного указателя). Оболочка вокруг исходного указателя - это то, что, по моему мнению, документация называется "принадлежащим дескриптору".

Ответ 3

std::sync::Arc - это умный указатель, который добавляет следующие возможности:

Обозначенная атомарная ссылка обертки для общего состояния.

Arc (и его небезобезопасный друг std::rc::Rc) позволяют совместное владение. Это означает, что несколько "ручек" указывают на одно и то же значение. Всякий раз, когда дескриптор клонирован, счетчик ссылок увеличивается. Всякий раз, когда дескриптор отбрасывается, счетчик уменьшается. Когда счетчик обращается в нуль, значение, которое указываются ручками, освобождается.

Обратите внимание, что этот интеллектуальный указатель не вызывает базовый метод clone данных; на самом деле, не может быть базового метода clone! Arc обрабатывает то, что происходит при вызове clone.

Что такое новый "владеющий дескриптор"? Это похоже на ссылку на данные?

Это и есть и не является ссылкой. В более широком программировании и английском смысле слова "ссылка" он является ссылкой. В специфическом смысле ссылки Rust (&Foo) она не является ссылкой. Смущает, правильно?


Вторая часть вашего вопроса о std::sync::Mutex, которая описывается как:

Примитив взаимного исключения, полезный для защиты общих данных

Мьютексы являются общими инструментами в многопоточных программах и хорошо описаны в другом месте, поэтому я не буду повторять это здесь. Важно отметить, что Rust Mutex дает вам возможность изменять общее состояние. До Arc разрешено нескольким владельцам иметь доступ к Mutex, чтобы даже попытаться изменить состояние.

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