|
|
@@ -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)
|
|
|
+```
|