today's studying
This commit is contained in:
parent
171c8d5489
commit
71db955e55
10 changed files with 446 additions and 2 deletions
|
|
@ -2,3 +2,5 @@
|
|||
- bees are unable to distinguish between red, black, and various grays
|
||||
straightforward - ![[Pasted image 20251105170221.png]]
|
||||
|
||||
contiguous - смежный
|
||||
compassionate - сострадательный
|
||||
7
10-linux/00-utils/todo.md
Normal file
7
10-linux/00-utils/todo.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
https://github.com/ducaale/xh
|
||||
https://github.com/Orange-OpenSource/hurl
|
||||
https://github.com/rgwood/systemctl-tui
|
||||
https://github.com/rcoh/angle-grinder
|
||||
https://github.com/stalwartlabs/stalwart
|
||||
https://github.com/FedericoBruzzone/tgt
|
||||
https://github.com/tramhao/termusic
|
||||
|
|
@ -86,3 +86,61 @@
|
|||
|
||||
**Главный вывод для Rust-разработчика:**
|
||||
Думай о памяти как о **ленте**, которую нужно читать подряд. Любой указатель (`Box`, ссылка, узел графа) — это разрыв ленты, который стоит дорого. Структуры данных, которые минимизируют эти разрывы (как B-Tree или `Vec`), всегда будут побеждать на современном железе.
|
||||
|
||||
|
||||
Это отличный вопрос. Твоя ментальная модель ("загружается в L1, потом копируется в L2") немного **перевернута**. В реальности все происходит ровно наоборот по направлению, но гораздо интереснее в деталях.
|
||||
|
||||
Давай разберем анатомию клонирования (например, строки или вектора) на уровне железа. Представим, что мы делаем `let b = a.clone();`.
|
||||
|
||||
### 1. Иерархия: Путь еды (данных)
|
||||
Сначала важно понять: процессор (ядро) **слеповат**. Он может работать только с тем, что лежит у него "в руках" — в **регистрах** (крошечная память прямо внутри ядра).
|
||||
|
||||
* **RAM (Оперативная память):** Это "склад" в соседнем здании. Огромный, но медленный.
|
||||
* **L3 Кэш:** Это "разгрузочная зона" на этаже. Общая для всех ядер.
|
||||
* **L2 Кэш:** Это "холодильник" на кухне конкретного ядра.
|
||||
* **L1 Кэш:** Это "разделочная доска" прямо перед руками повара. Самая быстрая.
|
||||
|
||||
### 2. Процесс клонирования (пошагово)
|
||||
|
||||
Когда ты вызываешь `clone()`, происходит операция **Read (чтение источника)** + **Write (запись копии)**.
|
||||
|
||||
#### Этап А: Чтение (Read) — "Доставка на кухню"
|
||||
Процессор говорит: "Мне нужны данные по адресу `A` (источник)".
|
||||
1. **Поиск:** Он ищет их в L1. Если нет — в L2. Если нет — в L3. Если нет — идет в RAM.
|
||||
2. **Cache Line Fill:** Данные никогда не ходят по одному байту. Они ходят "пачками" по **64 байта** (Cache Line). Даже если тебе нужен 1 байт, из RAM вытащится вся линия (64 байта) и по цепочке RAM -> L3 -> L2 -> L1 приедет к ядру.
|
||||
3. **Регистры:** Наконец, данные (или их часть) попадают в регистры процессора (например, в 256-битные регистры AVX, если копируем быстро).
|
||||
|
||||
#### Этап Б: Запись (Write) — "Создание клона"
|
||||
Теперь процессор должен записать эти данные по новому адресу `B` (куда мы клонируем).
|
||||
1. **Allocation:** Сначала Rust (аллокатор) находит свободное место в памяти для `B`.
|
||||
2. **Store (Запись):** Процессор пишет данные из регистров **в L1 кэш** по адресу `B`.
|
||||
* *Важный нюанс:* Он **не пишет** сразу в RAM! Это было бы безумно медленно.
|
||||
* Он пишет в L1 и помечает эту строку как **Dirty** (Грязная). Это значит: "Эта версия новее, чем то, что лежит в RAM. Не потеряй".
|
||||
|
||||
#### Этап В: Вытеснение (Eviction) — "Уборка"
|
||||
Вот тут рушится твое предположение про "копируется в L2". Данные попадают в L2 не потому что они "нужны там для работы", а потому что в L1 **закончилось место**.
|
||||
1. Когда L1 переполняется, старые или "грязные" данные вытесняются вниз — в L2.
|
||||
2. Когда L2 переполняется — в L3.
|
||||
3. И только когда L3 переполняется или процессор явно командует (сброс кэша), данные улетают в RAM.
|
||||
|
||||
***
|
||||
|
||||
### 3. "Кишечные" оптимизации (SIMD и Prefetching)
|
||||
|
||||
Если бы процессор копировал байт за байтом через обычные регистры (по 64 бита), это было бы медленно. В Rust (и в `libc` memcpy) клонирование оптимизировано жестко:
|
||||
|
||||
1. **SIMD (Single Instruction, Multiple Data):**
|
||||
Процессор использует широкие регистры (AVX/SSE). За один такт он может "вдохнуть" 32 или 64 байта данных источника в регистр и "выдохнуть" их в новое место в L1. Это как переносить воду ведрами, а не ложками.
|
||||
|
||||
2. **Non-Temporal Stores (Обход кэша):**
|
||||
Это хардкорная тема. Если ты клонируешь **гигантский** массив (больше размера кэша, например 100 МБ), процессор понимает: *"Если я сейчас запихну эти 100 МБ в L1, я вытесню оттуда все полезные данные моей программы, а этот массив мне сейчас не нужен для вычислений, я просто копирую"*.
|
||||
В этом случае используются специальные инструкции (например, `movntdq` в x86), которые пишут данные **мимо L1 и L2**, сразу в буфер записи (Write Combining Buffer) и оттуда в RAM. Это называется "не засорять кэш" (cache pollution).
|
||||
|
||||
### Итог для Rust-разработчика
|
||||
|
||||
Когда ты делаешь `clone()`:
|
||||
1. **CPU:** Читает кусок памяти `A` в регистры (протаскивая через все кэши вверх).
|
||||
2. **CPU:** Пишет из регистров в L1 по адресу `B`.
|
||||
3. **Кэш-контроллер:** Асинхронно, в фоновом режиме, когда-нибудь потом спускает эти данные из L1 вниз до RAM.
|
||||
|
||||
Взаимодействие с оперативной памятью **есть**, но оно отложено во времени (Write-Back), чтобы твой код не тормозил, ожидая записи в медленную RAM.
|
||||
27
20-dev/00-rust/20-dictionary/Untitled.md
Normal file
27
20-dev/00-rust/20-dictionary/Untitled.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
```rust
|
||||
methods:
|
||||
.to_lowercase()
|
||||
.flat_map()
|
||||
.par_iter()
|
||||
.map()
|
||||
.filter()
|
||||
.fold()
|
||||
.collect()
|
||||
.copied()
|
||||
.filter_map()
|
||||
.windows()
|
||||
.any()
|
||||
.all()
|
||||
.position()
|
||||
|
||||
|
||||
crates:
|
||||
use memchr::memmem;
|
||||
|
||||
```
|
||||
|
||||
|Префикс|Что делает (Технический смысл)|Пример|Стоимость (Cost)|
|
||||
|---|---|---|---|
|
||||
|as_|View / Borrow. Бесплатное преобразование типа, работающее с ссылкой.|as_bytes()|Нулевая (Zero-cost)|
|
||||
|to_|Clone / Allocate. Создает новую копию данных (тяжелая операция).|to_string(),to_vec()|Аллокация памяти|
|
||||
|into_|Consume. Потребляет (съедает) переменную. После этого старая переменная недоступна.|into_iter()|Обычно дешево (перемещение указателя)|
|
||||
81
20-dev/00-rust/20-livecoding/sum_of_squares.md
Normal file
81
20-dev/00-rust/20-livecoding/sum_of_squares.md
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
```rust
|
||||
use std::thread;
|
||||
|
||||
pub fn sum_squares_scope(data: &[i32]) -> i32 {
|
||||
let num_threads = 4;
|
||||
let chunk_size = data.len().div_ceil(num_threads); // (len + n - 1) / n
|
||||
|
||||
// thread::scope гарантирует, что все потоки внутри него
|
||||
// завершатся до закрывающей скобки '}'
|
||||
thread::scope(|s| {
|
||||
let mut handles = Vec::with_capacity(num_threads);
|
||||
|
||||
for chunk in data.chunks(chunk_size) {
|
||||
// Мы передаем ссылку `chunk` (&[i32]) внутрь потока.
|
||||
// Это разрешено ТОЛЬКО внутри scope, потому что Rust знает:
|
||||
// данные (data) точно переживут эти потоки.
|
||||
handles.push(s.spawn(move || {
|
||||
chunk.iter().map(|&x| x * x).sum::<i32>()
|
||||
}));
|
||||
}
|
||||
|
||||
// Собираем результаты
|
||||
handles.into_iter().map(|h| h.join().unwrap()).sum()
|
||||
})
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```rust
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::iter::Sum;
|
||||
use std::ops::Mul;
|
||||
|
||||
// T: Copy + Mul<Output=T> + Sum + Send + Sync + 'static
|
||||
// Copy - чтобы числа копировались (дешево)
|
||||
// Mul - чтобы умножать
|
||||
// Sum - чтобы складывать
|
||||
// Send + Sync - чтобы передавать между потоками
|
||||
// 'static - нужно для thread::spawn (если без scope)
|
||||
|
||||
pub fn sum_squares_generic<T>(data: Vec<T>) -> T
|
||||
where
|
||||
T: Copy + Mul<Output = T> + Sum + Send + Sync + 'static
|
||||
{
|
||||
let num_threads = thread::available_parallelism()
|
||||
.map(|n| n.get())
|
||||
.unwrap_or(1);
|
||||
|
||||
// Zero-copy: превращаем Vec<T> в Arc<[T]>
|
||||
// Это дешево, не копирует данные (просто меняет метаданные аллокации)
|
||||
let data_arc: Arc<[T]> = Arc::from(data);
|
||||
|
||||
let chunk_size = data_arc.len().div_ceil(num_threads);
|
||||
let mut handles = Vec::with_capacity(num_threads);
|
||||
|
||||
for i in 0..num_threads {
|
||||
let data_ref = Arc::clone(&data_arc);
|
||||
|
||||
handles.push(thread::spawn(move || {
|
||||
let start = i * chunk_size;
|
||||
let end = std::cmp::min(start + chunk_size, data_ref.len());
|
||||
|
||||
if start >= data_ref.len() {
|
||||
// Нам нужно вернуть "ноль" (нейтральный элемент суммы).
|
||||
// Так как T - дженерик, мы не можем просто написать 0.
|
||||
// Хитрость: сумма пустого итератора дает ноль типа T.
|
||||
return std::iter::empty::<T>().sum();
|
||||
}
|
||||
|
||||
data_ref[start..end].iter()
|
||||
.map(|&x| x * x)
|
||||
.sum()
|
||||
}));
|
||||
}
|
||||
|
||||
handles.into_iter().map(|h| h.join().unwrap()).sum()
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
70
20-dev/00-rust/30-fearless/Untitled.md
Normal file
70
20-dev/00-rust/30-fearless/Untitled.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
Отличный и глубокий вопрос! На него часто дают неполный ответ ("перемещается замыкание"), но давайте разберем **конкретно по байтам**, что именно физически перемещается в новый поток в вашем коде.
|
||||
|
||||
### Короткий ответ
|
||||
Когда вы пишете `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)
|
||||
175
20-dev/00-rust/30-fearless/exersizes.md
Normal file
175
20-dev/00-rust/30-fearless/exersizes.md
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
Прекрасная цель. Переход от однопоточного мышления к многопоточному (Concurrent Thinking) — это именно то, что отличает Junior/Middle от Senior Systems Engineer.
|
||||
|
||||
В однопоточном коде время линейно: **A -> B -> C**.
|
||||
В многопоточном коде время "разветвляется", и вы теряете детерминизм: **A** происходит, а **B** и **C** могут случиться в любом порядке, одновременно или **B** может начаться, прерваться на середине, пропустить **C**, а потом продолжиться.
|
||||
|
||||
Чтобы научиться "думать потоками", нужно пройти через три этапа "боли". Давайте я дам вам конкретные упражнения (Kata) без использования библиотек типа Rayon/Tokio, только `std::thread` и `std::sync`.
|
||||
|
||||
Вот ваша дорожная карта от "однопоточника" до "системщика".
|
||||
|
||||
### Уровень 1: Shared State (Разделяемое состояние)
|
||||
*Суть: Как не сломать данные, когда их трогают все сразу.*
|
||||
|
||||
**Задача: "Банковский симулятор"**
|
||||
У вас есть несколько счетов и несколько потоков, которые переводят деньги туда-сюда.
|
||||
* **Сложность:** Если поток А переводит со счета 1 на счет 2, а поток Б переводит со счета 2 на счет 1 одновременно, может возникнуть **Deadlock** (взаимная блокировка). Оба захватят первый мьютекс и будут вечно ждать второго.
|
||||
|
||||
**Челлендж для реализации:**
|
||||
1. Создайте структуру `Bank` с вектором `Mutex<i32>` (счета).
|
||||
2. Напишите функцию `transfer(from_id, to_id, amount)`.
|
||||
3. **Главная цель:** Реализовать это так, чтобы при 1000 одновременных рандомных переводов программа не зависла (deadlock) и сумма всех денег в банке осталась неизменной (invariant).
|
||||
|
||||
*Подсказка сеньора:* Блокировки всегда нужно брать в определенном порядке (например, всегда блокировать меньший ID счета первым).
|
||||
|
||||
### Уровень 2: Coordination & Signaling (Координация)
|
||||
*Суть: Как заставить потоки ждать друг друга без `sleep`.*
|
||||
|
||||
**Задача: "Пинг-Понг" (или Producer-Consumer)**
|
||||
Два потока. Один печатает "Ping", другой "Pong".
|
||||
Строго по очереди!
|
||||
Вывод должен быть: `Ping, Pong, Ping, Pong...`
|
||||
Нельзя использовать: `channel` (каналы).
|
||||
Нужно использовать: `Condvar` (Condition Variable) и `Mutex`.
|
||||
|
||||
**Зачем это нужно:**
|
||||
Вы поймете, что такое *spurious wakeups* (ложные пробуждения) и почему паттерн всегда выглядит как:
|
||||
```rust
|
||||
while !condition { // <-- Важно: WHILE, а не IF
|
||||
cvar.wait(guard).unwrap();
|
||||
}
|
||||
```
|
||||
Это база построения любых очередей задач.
|
||||
|
||||
### Уровень 3: Lock-free (Атомики и память)
|
||||
*Суть: Максимальная скорость, работа с инструкциями процессора.*
|
||||
|
||||
**Задача: "Spinlock" (Спин-блокировка)**
|
||||
Реализуйте свой собственный мьютекс, используя только `AtomicBool` и `Compare-and-Swap`.
|
||||
Стандартный `Mutex` усыпляет поток (syscall к ОС), а ваш должен крутиться в цикле `while`, пока не захватит флаг.
|
||||
|
||||
```rust
|
||||
pub struct MySpinLock<T> {
|
||||
locked: AtomicBool,
|
||||
data: UnsafeCell<T>, // Придется немного потрогать unsafe, это полезно для понимания
|
||||
}
|
||||
```
|
||||
|
||||
**Челлендж:**
|
||||
Поймите, почему ваш спинлок греет процессор на 100% и как `std::thread::yield_now()` помогает это исправить.
|
||||
|
||||
***
|
||||
|
||||
### Практический пример для разбора прямо сейчас
|
||||
|
||||
Давайте разберем **Deadlock** — самую частую ошибку новичков после гонок данных.
|
||||
|
||||
Попробуйте в уме (или в коде) предсказать, что случится здесь:
|
||||
|
||||
```rust
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
fn main() {
|
||||
let resource_a = Arc::new(Mutex::new(0));
|
||||
let resource_b = Arc::new(Mutex::new(0));
|
||||
|
||||
let a = resource_a.clone();
|
||||
let b = resource_b.clone();
|
||||
|
||||
// Поток 1: Хочет A, потом B
|
||||
let t1 = thread::spawn(move || {
|
||||
let _guard_a = a.lock().unwrap();
|
||||
println!("T1 взял A");
|
||||
thread::sleep(std::time::Duration::from_millis(100)); // Имитация работы
|
||||
let _guard_b = b.lock().unwrap(); // Ждет B
|
||||
println!("T1 взял B");
|
||||
});
|
||||
|
||||
let a = resource_a.clone();
|
||||
let b = resource_b.clone();
|
||||
|
||||
// Поток 2: Хочет B, потом A
|
||||
let t2 = thread::spawn(move || {
|
||||
let _guard_b = b.lock().unwrap();
|
||||
println!("T2 взял B");
|
||||
thread::sleep(std::time::Duration::from_millis(100));
|
||||
let _guard_a = a.lock().unwrap(); // Ждет A
|
||||
println!("T2 взял A");
|
||||
});
|
||||
|
||||
t1.join().unwrap();
|
||||
t2.join().unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
**Результат:** Программа напечатает "T1 взял A", "T2 взял B" и зависнет навсегда.
|
||||
Это классический deadlock. Поток 1 держит A и ждет B. Поток 2 держит B и ждет A.
|
||||
|
||||
**Как мыслит Сеньор:**
|
||||
1. *"Ага, вложенные блокировки (`lock` внутри другого `lock`). Это красная зона."*
|
||||
2. *"Есть ли гарантия порядка захвата?"* (В коде выше — нет, порядок обратный).
|
||||
3. **Решение:** Всегда захватывать ресурсы в **одном и том же глобальном порядке**. Если всем потокам сначала нужен А, а потом Б — дедлока не будет.
|
||||
|
||||
**Предлагаю сделку:**
|
||||
Реализуйте простую версию **"Пинг-Понга"** (Уровень 2) на `Condvar`. Это лучшее упражнение, чтобы почувствовать, как потоки "разговаривают" друг с другом, передавая управление. Если застрянете — я подскажу, но сначала попробуйте сами. Это даст вам тот самый "щелчок" понимания.
|
||||
|
||||
|
||||
Да, на Exercism в треке Rust есть отличные упражнения именно на эту тему. Они там считаются "классикой", через которую обязан пройти каждый "раст-конкаренси-бой".[1][2]
|
||||
|
||||
Вот **Топ-3**, которые идеально ложатся на вашу цель (от простого к сложному):
|
||||
|
||||
### 1. Parallel Letter Frequency (`parallel-letter-frequency`)
|
||||
* **Суть:** То, что мы с вами обсуждали. Дан список текстов, нужно подсчитать частоту букв, используя потоки.[3][4]
|
||||
* **Чему учит:**
|
||||
* `std::thread::spawn` vs Rayon (можно решить и так, и так).
|
||||
* Как правильно делить данные (Data Partitioning).
|
||||
* Использование `HashMap` в многопоточной среде (Merge vs Shared Mutex).
|
||||
* **Ваш челлендж:** Решите это сначала через `thread::spawn` + `mpsc` (каналы), а потом через `thread::scope` и возвращаемые значения. Сравните бенчмарки.
|
||||
|
||||
### 2. Dining Philosophers ("Обедающие философы")
|
||||
* **Суть:** 5 философов, 5 вилок. Чтобы поесть, нужно две вилки. Если все возьмут левую вилку одновременно — **Deadlock** (все ждут правую вечно).[5][6][7]
|
||||
* **Чему учит:**
|
||||
* Управление ресурсами (`Mutex`).
|
||||
* Избегание Deadlock (Resource Ordering).
|
||||
* Работа с `Arc` для шаринга вилок.
|
||||
* **Ваш челлендж:** Напишите решение, где философы всегда едят, и никто не голодает. Попробуйте реализовать это через "Арбитра" (официанта) или через стратегию "Чётный-Нечётный".
|
||||
|
||||
### 3. Circular Buffer (Кольцевой буфер)
|
||||
* **Суть:** Реализовать структуру данных, в которую можно писать и читать.
|
||||
* **Чему учит:**
|
||||
* Если усложнить задачу до *Concurrent Circular Buffer* (хотя в базе она однопоточная), то это идеальный полигон для **Atomics**.
|
||||
* Попробуйте сделать его `Lock-Free`. Это уже уровень "God Mode".
|
||||
|
||||
### Где их найти:
|
||||
На сайте Exercism.org в треке Rust.
|
||||
1. Зарегистрируйтесь (бесплатно).
|
||||
2. `exercism download --exercise=parallel-letter-frequency --track=rust`
|
||||
3. Решайте локально в NeoVim/Helix, запускайте `cargo test`.
|
||||
|
||||
### Бонус: Comprehensive Rust (от Google)
|
||||
У Google есть курс **Comprehensive Rust**, и там есть отдельный раздел "Concurrency" с упражнениями.
|
||||
Особенно упражнение **"Dining Philosophers"** там разобрано очень детально, с вариантами решений через каналы и мьютексы.[6][8]
|
||||
* Ссылка: `google.github.io/comprehensive-rust/concurrency/`
|
||||
|
||||
**Совет:** Начните с **Dining Philosophers**. Это упражнение сломает вам мозг (в хорошем смысле) именно на тему "как потоки мешают друг другу", а не просто "как ускорить вычисления".
|
||||
|
||||
[1](https://exercism.org/tracks/rust/exercises)
|
||||
[2](https://exercism.org/tracks/rust)
|
||||
[3](https://exercism.org/tracks/rust/exercises/parallel-letter-frequency)
|
||||
[4](https://exercism.org/tracks/rust/exercises/parallel-letter-frequency/solutions/tommilligan)
|
||||
[5](http://www.akira.ruc.dk/~keld/teaching/ipdc_f10/exercises/exercise1.pdf)
|
||||
[6](https://google.github.io/comprehensive-rust/concurrency/sync-exercises/dining-philosophers.html)
|
||||
[7](https://stackoverflow.com/questions/31912781/why-doesnt-the-dining-philosophers-exercise-deadlock-if-done-incorrectly)
|
||||
[8](https://comprehensive-rust.pages.dev/exercises/concurrency/dining-philosophers.html)
|
||||
[9](http://forum.exercism.org/t/rust-syllabus-overview-tracking/5346)
|
||||
[10](http://forum.exercism.org/t/rust-syllabus-overview-tracking/5346?page=2)
|
||||
[11](https://exercism.org/tracks/rust/exercises/parallel-letter-frequency/solutions/LU15W1R7H)
|
||||
[12](http://forum.exercism.org/t/rust-track-exercise-order/10855)
|
||||
[13](https://exercism.org/exercises/parallel-letter-frequency)
|
||||
[14](https://github.com/exercism/rust)
|
||||
[15](https://github.com/google/comprehensive-rust/discussions/1313)
|
||||
[16](https://www.youtube.com/watch?v=Sf4tf-4d2ag)
|
||||
[17](http://forum.exercism.org/t/48in24-exercise-03-12-parallel-letter-frequency/9927)
|
||||
[18](https://exercism.org/tracks/tcl)
|
||||
[19](https://www.youtube.com/watch?v=bkNQ1_kGHYE)
|
||||
[20](http://forum.exercism.org/t/would-be-nice-to-add-concurrency-to-the-concept-tree-in-the-go-track/4539)
|
||||
19
20-dev/00-rust/30-fearless/mpsc.md
Normal file
19
20-dev/00-rust/30-fearless/mpsc.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
Круто!
|
||||
|
||||
```rust
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
|
||||
fn main() {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
thread::spawn(move || {
|
||||
let val = String::from("hi");
|
||||
tx.send(val).unwrap();
|
||||
});
|
||||
|
||||
let received = rx.recv().unwrap();
|
||||
println!("Got: {received}");
|
||||
}
|
||||
```
|
||||
|
||||
3
20-dev/00-rust/40-lifetimes-str/str и &str.md
Normal file
3
20-dev/00-rust/40-lifetimes-str/str и &str.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
- **`str` (String Slice)**: Это "неразмеренный" (unsized) тип. Вы даже не можете положить его в переменную напрямую (в стек), только через ссылку. Так как его нельзя просто так взять и "побайтить" без знания размера, он не `Copy`.
|
||||
|
||||
- **`&str` (Shared Reference to str)**: Это "жирная ссылка" (fat pointer) — указатель + длина. Она имеет фиксированный размер (обычно 16 байт на 64-битных системах). Этот тип **реализует `Copy`**
|
||||
|
|
@ -6,6 +6,8 @@ clickhouse(?)
|
|||
voice2text
|
||||
rendering
|
||||
video
|
||||
video-anal
|
||||
video-analyt
|
||||
|
||||
Rust (tokio, axum, sqlx), docker/kubernetes, Postgresql, NoSQL БД (Redis, MongoDB), REST/gRPC protobuf, RabbitMQ, S3.
|
||||
|
||||
rsync
|
||||
Loading…
Add table
Add a link
Reference in a new issue