70 lines
No EOL
5.2 KiB
Markdown
70 lines
No EOL
5.2 KiB
Markdown
Отличный и глубокий вопрос! На него часто дают неполный ответ ("перемещается замыкание"), но давайте разберем **конкретно по байтам**, что именно физически перемещается в новый поток в вашем коде.
|
||
|
||
### Короткий ответ
|
||
Когда вы пишете `move ||`, в новый поток перемещается **сама структура замыкания** (closure struct).
|
||
В вашем случае эта структура имеет размер всего 8 байт (на 64-битной системе) и содержит внутри **только один `Arc`**.
|
||
|
||
### Подробный разбор ("под капот")
|
||
|
||
#### 1. Что такое замыкание в Rust?
|
||
Компилятор Rust превращает ваше замыкание в анонимную структуру (struct). Поля этой структуры — это все переменные из внешнего окружения, которые вы захватили.
|
||
|
||
В вашем коде внутри `thread::spawn`:
|
||
```rust
|
||
move || {
|
||
// Используются:
|
||
// 1. i (копия, примитив)
|
||
// 2. chunk_size (копия, примитив)
|
||
// 3. data_ref (это Arc<Vec<i32>>)
|
||
|
||
// ... логика
|
||
}
|
||
```
|
||
Компилятор генерирует примерно такую структуру:
|
||
|
||
```rust
|
||
// Псевдокод сгенерированной структуры
|
||
struct ClosureEnvironment {
|
||
i: usize, // 8 байт
|
||
chunk_size: usize, // 8 байт
|
||
data_ref: Arc<Vec<i32>>, // 8 байт (указатель на кучу)
|
||
}
|
||
```
|
||
|
||
#### 2. Что делает `move`?
|
||
Ключевое слово `move` приказывает компилятору: "Забери владение всеми переменными, которые используются внутри `{...}`, и положи их в эту структуру `ClosureEnvironment`".
|
||
|
||
Без `move` компилятор попытался бы положить в структуру **ссылки** (`&i`, `&data_ref`), что вызвало бы ошибку компиляции, так как ссылки не могут пережить функцию `sum_squares_static_arc`.
|
||
|
||
#### 3. Что физически "перемещается"?
|
||
Когда вы вызываете `thread::spawn(move || ...)`:
|
||
1. Создается экземпляр этой структуры `ClosureEnvironment` на стеке текущего потока.
|
||
2. Этот экземпляр **перемещается** (memcpy) из стека главного потока в стек нового потока (или в кучу, если `Box::new`).
|
||
* `i` и `chunk_size` просто копируются (как `Copy` типы).
|
||
* `data_ref` (`Arc`) тоже копируется (бинарно это просто указатель `0x...`). **Сами данные массива `[i32]` никуда не копируются**, они лежат в куче и остаются на месте. Копируется только "умный указатель" на них.
|
||
|
||
### Итоговая картина
|
||
В новый поток "улетает" маленький пакет данных (размером 24 байта: `usize` + `usize` + `ptr`), который содержит:
|
||
1. Число `i` (индекс потока).
|
||
2. Число `chunk_size`.
|
||
3. Указатель `Arc` (который увеличил счетчик ссылок на данные, но сами данные не трогал).
|
||
|
||
В этом и есть суть эффективности: вы передаете гигабайты данных в другой поток, физически перемещая всего 8 байт указателя `Arc`.
|
||
|
||
### Важное уточнение про `Arc`
|
||
Обратите внимание, что перед циклом вы делаете:
|
||
```rust
|
||
let data_ref = Arc::clone(&data_arc);
|
||
```
|
||
Именно этот **новый** `data_ref` перемещается в замыкание. Оригинальный `data_arc` остается в `main` потоке нетронутым.
|
||
Каждая итерация цикла создает свой собственный "билетик" (`data_ref`), и именно этот билетик `move` забирает в новый поток.
|
||
|
||
[1](https://doc.rust-lang.org/std/sync/struct.Arc.html)
|
||
[2](https://stackoverflow.com/questions/72251938/implement-thread-safe-function-object-in-rust)
|
||
[3](http://rust-unofficial.github.io/too-many-lists/third-arc.html)
|
||
[4](https://users.rust-lang.org/t/implementing-thread-safe-structs-for-tokio/21658)
|
||
[5](https://onesignal.com/blog/thread-safety-rust/)
|
||
[6](https://www.rustfinity.com/blog/what-is-thread-safety)
|
||
[7](https://rust-training.ferrous-systems.com/latest/book/thread-safety)
|
||
[8](https://forums.swift.org/t/arc-automatic-reference-counting-is-thread-safe/63899)
|
||
[9](https://www.ardanlabs.com/blog/2024/11/fearless-concurrency-ep6-understanding-rust-arc-for-efficient-multithreading.html) |