From 71db955e558ea8a4a7644fded074fdabccea2e56 Mon Sep 17 00:00:00 2001 From: Rorik Star Platinum Date: Thu, 4 Dec 2025 23:25:29 +0300 Subject: [PATCH] today's studying --- 00-english/vovabulary.md | 2 + 10-linux/00-utils/todo.md | 7 + .../Кэш, регистры, кэш-линия.md | 60 +++++- 20-dev/00-rust/20-dictionary/Untitled.md | 27 +++ .../00-rust/20-livecoding/sum_of_squares.md | 81 ++++++++ 20-dev/00-rust/30-fearless/Untitled.md | 70 +++++++ 20-dev/00-rust/30-fearless/exersizes.md | 175 ++++++++++++++++++ 20-dev/00-rust/30-fearless/mpsc.md | 19 ++ 20-dev/00-rust/40-lifetimes-str/str и &str.md | 3 + 30-money/Untitled.md | 4 +- 10 files changed, 446 insertions(+), 2 deletions(-) create mode 100644 10-linux/00-utils/todo.md create mode 100644 20-dev/00-rust/20-dictionary/Untitled.md create mode 100644 20-dev/00-rust/20-livecoding/sum_of_squares.md create mode 100644 20-dev/00-rust/30-fearless/Untitled.md create mode 100644 20-dev/00-rust/30-fearless/exersizes.md create mode 100644 20-dev/00-rust/30-fearless/mpsc.md create mode 100644 20-dev/00-rust/40-lifetimes-str/str и &str.md diff --git a/00-english/vovabulary.md b/00-english/vovabulary.md index 396cd9f..b24d2c8 100644 --- a/00-english/vovabulary.md +++ b/00-english/vovabulary.md @@ -2,3 +2,5 @@ - bees are unable to distinguish between red, black, and various grays straightforward - ![[Pasted image 20251105170221.png]] +contiguous - смежный +compassionate - сострадательный \ No newline at end of file diff --git a/10-linux/00-utils/todo.md b/10-linux/00-utils/todo.md new file mode 100644 index 0000000..6d1690b --- /dev/null +++ b/10-linux/00-utils/todo.md @@ -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 \ No newline at end of file diff --git a/10-linux/30-computer-arch/Кэш, регистры, кэш-линия.md b/10-linux/30-computer-arch/Кэш, регистры, кэш-линия.md index 11347ec..d4cf836 100644 --- a/10-linux/30-computer-arch/Кэш, регистры, кэш-линия.md +++ b/10-linux/30-computer-arch/Кэш, регистры, кэш-линия.md @@ -85,4 +85,62 @@ | **TLB** | Кэш адресов памяти | Хаотичные прыжки по памяти (Pointer Chasing) забивают не только кэш данных, но и TLB. | **Главный вывод для Rust-разработчика:** -Думай о памяти как о **ленте**, которую нужно читать подряд. Любой указатель (`Box`, ссылка, узел графа) — это разрыв ленты, который стоит дорого. Структуры данных, которые минимизируют эти разрывы (как B-Tree или `Vec`), всегда будут побеждать на современном железе. \ No newline at end of file +Думай о памяти как о **ленте**, которую нужно читать подряд. Любой указатель (`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. \ No newline at end of file diff --git a/20-dev/00-rust/20-dictionary/Untitled.md b/20-dev/00-rust/20-dictionary/Untitled.md new file mode 100644 index 0000000..2b7f078 --- /dev/null +++ b/20-dev/00-rust/20-dictionary/Untitled.md @@ -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()|Обычно дешево (перемещение указателя)| diff --git a/20-dev/00-rust/20-livecoding/sum_of_squares.md b/20-dev/00-rust/20-livecoding/sum_of_squares.md new file mode 100644 index 0000000..2f3c0f4 --- /dev/null +++ b/20-dev/00-rust/20-livecoding/sum_of_squares.md @@ -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::() + })); + } + + // Собираем результаты + 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 + Sum + Send + Sync + 'static +// Copy - чтобы числа копировались (дешево) +// Mul - чтобы умножать +// Sum - чтобы складывать +// Send + Sync - чтобы передавать между потоками +// 'static - нужно для thread::spawn (если без scope) + +pub fn sum_squares_generic(data: Vec) -> T +where + T: Copy + Mul + Sum + Send + Sync + 'static +{ + let num_threads = thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(1); + + // Zero-copy: превращаем Vec в 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::().sum(); + } + + data_ref[start..end].iter() + .map(|&x| x * x) + .sum() + })); + } + + handles.into_iter().map(|h| h.join().unwrap()).sum() +} + +``` + diff --git a/20-dev/00-rust/30-fearless/Untitled.md b/20-dev/00-rust/30-fearless/Untitled.md new file mode 100644 index 0000000..64f22cd --- /dev/null +++ b/20-dev/00-rust/30-fearless/Untitled.md @@ -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>) + + // ... логика +} +``` +Компилятор генерирует примерно такую структуру: + +```rust +// Псевдокод сгенерированной структуры +struct ClosureEnvironment { + i: usize, // 8 байт + chunk_size: usize, // 8 байт + data_ref: Arc>, // 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) \ No newline at end of file diff --git a/20-dev/00-rust/30-fearless/exersizes.md b/20-dev/00-rust/30-fearless/exersizes.md new file mode 100644 index 0000000..5ad3d89 --- /dev/null +++ b/20-dev/00-rust/30-fearless/exersizes.md @@ -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` (счета). +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 { + locked: AtomicBool, + data: UnsafeCell, // Придется немного потрогать 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) \ No newline at end of file diff --git a/20-dev/00-rust/30-fearless/mpsc.md b/20-dev/00-rust/30-fearless/mpsc.md new file mode 100644 index 0000000..2c84326 --- /dev/null +++ b/20-dev/00-rust/30-fearless/mpsc.md @@ -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}"); +} +``` + diff --git a/20-dev/00-rust/40-lifetimes-str/str и &str.md b/20-dev/00-rust/40-lifetimes-str/str и &str.md new file mode 100644 index 0000000..b29c4d6 --- /dev/null +++ b/20-dev/00-rust/40-lifetimes-str/str и &str.md @@ -0,0 +1,3 @@ +- **`str` (String Slice)**: Это "неразмеренный" (unsized) тип. Вы даже не можете положить его в переменную напрямую (в стек), только через ссылку. Так как его нельзя просто так взять и "побайтить" без знания размера, он не `Copy`. + +- **`&str` (Shared Reference to str)**: Это "жирная ссылка" (fat pointer) — указатель + длина. Она имеет фиксированный размер (обычно 16 байт на 64-битных системах). Этот тип **реализует `Copy`** diff --git a/30-money/Untitled.md b/30-money/Untitled.md index b0788b6..7f4b94e 100644 --- a/30-money/Untitled.md +++ b/30-money/Untitled.md @@ -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 \ No newline at end of file