2022-01-13 06:18:46
Начинаем день с 9-ой главы книги "Golang для профи" от Михалиса Цукалоса. Глава - весьма лёгкая, поэтому я записал либо общие термины, которые важно помнить, либо моменты, которые мне показались интересными.
Конкурентность и параллелизм - разные вещи. Параллелизм - возможность выполнить несколько задач параллельно, в один момент времени. Конкурентность - способ организации, при котором задачи выполняются тогда, когда это возможно. В случае go - когда одна задача уходит на ожидание, другая начинает выполнение. Если первая выходит из ожидания, пока вторая всё ещё выполняется - первая может продолжить выполняться в другом потоке.
Очень часто разработчики используют waitgroup для ожидания выполнения пачки горутин. Перед запуском горутины счетчик инкрементируется посредством waitgroup.Add(1), внутри горутины происходит декремент, посредством waitgroup.Done(), в основном потоке мы ждём завершения выполнения группы горутин с помощью waitgroup.Wait(). Если количество Add будет больше, чем Done - получим deadlock, если меньше - фатальное завершение, так что этот момент важно контролировать.
Канал - это механизм коммуникации, позволяющий обмениваться данными между горутинами. Канал создаётся с помощью make(chan type, bufferSize), где type - тип данных, проходящих через канал, а bufferSize - количество данных, которые канал сможет принять до блокировки. Канал можно закрыть с помощью метода close().
Если мы попытаемся считать данные из закрытого канала - мы получим нулевое значение соответствующего типа, а не ошибку. Это важный момент, часто являющийся поводом для ошибок, и который нужно обрабатывать.
Конвейер - паттерн, представляющий собой виртуальный метод, соединяющий горутины и каналы таким образом, что выходные данные одной горутины становятся входными данными для другой горутины.
Например:
var CLOSEA = false
var DATA = make(map[int]bool)
func random(min, max int) int {
return rand.Intn(max - min) + min
}
func first(min, max int, out chan <- int) {
for {
if CLOSEA {
close(out)
return
}
out <- random(min, max)
}
}
func second(out chan<- int, in <-chan int) {
for x := range in {
_, ok := DATA[x]
if ok {
CLOSEA = true
} else {
DATA[x] = true
out <- x
}
}
close(out)
}
func third(in <- chan int) {
var sum int
sum = 0
for x2 := range in {
sum = sum + x2
}
fmt.Println(sum)
}
func main() {
rand.Seed(time.Now().UnixNano())
A := make(chan int)
B := make(chan int)
go first()
go second()
third()
}
Похожим образом работает конвейер в unix: в консоли мы можем передать вывод одной программы на вход другой через вертикальный слеш: cat .env | grep psql
Далее автор приводит интересное сравнение моделей конкурентности в go и Rust:
* потоки Rust - это потоки UNIX, соответственно - они гораздо более тяжелые, чем горутины
* Rust поддерживает конкурентность на основе обмена сообщениями и на основе совместного использования состояний - то же самое, что в Go реализовано с помощью каналов, мьютексов и общих переменных
* строгая типизация и система владения позволяет Rust обеспечивать безопасное изменяемое состояние потока
* Rust позволяет, с помощью специальных структур, совместно использовать состояния потока
Резюмируя, у Rust есть гибкая модель конкурентности, ещё более гибкая, чем у go. Но, чтобы её использовать, приходится мириться с Rust :)
Далее - ещё одно сравнение, между Go и Erlang:
* в Erlang используется только асинхронная коммуникация
* в Erlang реализована система обработки ошибок, позволяющая создавать надёжные конкурентные системы
* сбои процессов во время конкурентной работы можно ловить и обрабатывать
* как и горутины, процессы в Erlang являются изолированными, у них нет общих состояний. Единственный способ взаимодействия между процессами - передача сообщений
* Потоки Erlang такие же лёгкие, как и горутины.
Система конкурентности в Erlang, в общих чертах, больше похожа на систему конкурентности в Go, так что если тебе нравится Erlang - можно смело его юзать :)
255 views03:18