Browse Source

SVI Добавление занятия 2

SVI 2 years ago
parent
commit
cd87f115e6
1 changed files with 162 additions and 0 deletions
  1. 162 0
      docs/lessons02.md

+ 162 - 0
docs/lessons02.md

@@ -0,0 +1,162 @@
+# Каналы
+
+## Содержание
+
+- [Каналы](#каналы)
+  - [Содержание](#содержание)
+  - [Данные в конкуретной среде](#данные-в-конкуретной-среде)
+  - [Каналы](#каналы-1)
+  - [Что не так с каналами](#что-не-так-с-каналами)
+    - [Как с этим жить](#как-с-этим-жить)
+  - [Неявные правила работа с данными](#неявные-правила-работа-с-данными)
+  - [Как определить есть ли что-то в канале](#как-определить-есть-ли-что-то-в-канале)
+    - [Что не так с длиной канала](#что-не-так-с-длиной-канала)
+  - [Примеры](#примеры)
+
+Каналы в `go` являются попыткой решить проблему безопасности данных в конкуретной среде.
+Это неидеальная попытка реализовать [монитор Хоара](https://ru.wikipedia.org/wiki/Монитор_(синхронизация)). Неидеальность продиктована тем, что подавляющая часть решений в `go` продиктована двумя соображениями:
+
+- простой язык заставляет думать над алгоритмом, а не способом проявить свой интеллект;
+- новый человек в коллективе должен легко читать незнакомый код;
+- при необходимости -- можно использовать опасные средства для увеличения эффективности, если ситуация навязывает такое решение;
+
+Все эти компромиссы имеют свою цену, поэтому не стоит увлекаться. **Каналы** именно такое средство.
+
+## Данные в конкуретной среде
+
+Основная проблема состоит в том, что конкурентные потоки могут обращаться к одним и тем же данных.
+
+Нет проблем, когда эти обращения только на чтение. Проблема появляется тогда, когда хотя бы один поток начинает менять данные. Без специальных мер невозможно гарантировать, что чтение будет происходить после записи, а не во время записи. Если такое состояние происходит -- вместо данных после чтения может оказаться мусор.
+
+Эта проблема получила название [гонок данных](https://ru.wikipedia.org/wiki/Состояние_гонки). Проблема далеко не умозрительная. [[Тут](https://ru.wikipedia.org/wiki/Therac-25)] можно почитать, как аппарат лучевой терапии из-за этой ошибки убивал пациентов (должен был лечить).
+
+| Поток-1 | Поток-2 | Результат |
+| ------- | ------- | --------- |
+| Пишет   | Читает  | Мусор     |
+| Пишет   | Пишет   | Мусор     |
+| Читает  | Пишет   | Мусор     |
+| Читает  | Читает  | **Норм**  |
+
+В 75% случаев -- будет мусор. **Полагаться на случай нельзя**.
+
+## Каналы
+
+Условная схема канала представлена ниже:
+
+```mermaid
+flowchart LR
+    subgraph chan
+        mutex
+        in
+        out
+        LIFO
+        subgraph property
+            len
+            cap
+            _isClose_
+        end
+    end
+    flow1 --> |data|in
+    mutex --> in
+    in --> LIFO
+    LIFO --> out
+    mutex --> LIFO
+    out --> |data|flow2
+    mutex --> out
+```
+
+На уровне рантайма это:
+
+- мьютекс;
+- очередь;
+- заданная ёмкость;
+- длина (сколько элементов из ёмкости занято);
+- признак "закрыт ли канал".
+
+## Что не так с каналами
+
+Канал требует строгой дисциплины из-за своих особенностей.
+
+- канал нельзя закрыть дважды -- будет паника;
+- нельзя записать в закрытый канал -- будет паника;
+- закрытый канал может содержать полезные данные.
+
+### Как с этим жить
+
+Простые правила помогут избежать непредсказуемого поведение программы:
+
+- кто канал создал -- тот в него и пишет;
+- кто канал создал -- тот его и закрывает;
+- кто канал создал -- тот и отдаёт канал, но _только для чтения_;
+- кто канал читает -- ничего с ним не делает.
+
+При соблюдении первых трёх правил -- в четвёртом случае ничего и не получится. Но часто придётся работать с каналами, которые не подчиняются первым трём правилам. Об это стоит помнить.
+
+## Неявные правила работа с данными
+
+Мало передать структуры и данные через каналы, чтобы избежать гонок данных.
+
+Надо понимать, что как только данные попали в канал _по ссылке_ -- теперь есть две ссылки,которые указывают на одни и теже данные.
+
+Поэтому два железных правила:
+
+- что попало в канал -- то пропало;
+- что попало в канал, но надо иметь доступ из нескольких потоков -- структура должна иметь охрану данных собственным мьютексом.
+
+Второе правило должно быть исключением. Если какой-либо глобальный объект разделяется между потоками -- он должен быть доступен через объект приложения, а не через каналы.
+
+## Как определить есть ли что-то в канале
+
+К каналу, как и к срезу применима функция `len`:
+
+```go
+countMsg := len(chMsg)
+if countMsg == 0{
+    return
+}
+// Что-то делаем не с пустым каналом
+```
+
+### Что не так с длиной канала
+
+Проблема в том, что в канал пишет один поток, а длину (обычно) проверяет другой.
+И пока второй поток что-то делает с каналом -- первый в это время может ещё дописать данных.
+
+**На текущую длину канала в потребителе полагаться нальзя!**
+
+Единственный надёжный метод -- итерация по каналу в цикле.
+
+## Примеры
+
+Первый пример показывает:
+
+- как правильно создать канал;
+- как правильно читать из канала;
+- как правильно закрыть канал.
+
+```go
+
+// Создаёт канал и пишет в него некоторое количество сообщений
+//   Этот же канал и  возвращает. Причём закрытый. Причём только для чтения.
+func runWriter()<- chan int{
+    chProduct := make(chan int, 1_000)
+    for i:=0; i<200; i++ {
+        chProduct <- i
+    }
+    // Закрытие канала здесь никак не влияет на способность потом читать из канала
+    close(chProduct)
+    return chProduct
+}
+
+// Получает канал при вызове и читает из него, пока не закончатся данные
+//  Не играет значения, что канал закрыт. Выход из цикла произойдёт автоматически
+//  по факту исчерпания данных.
+func runReader (chProd <-chan int){
+    for val := range chProd {
+        log.Printf("runReader(): i=%v\n", i)
+    }
+}
+
+chProd := runWriter()
+runReader(chProd)
+```