При работе с проблемой параллелизма сложнее рассуждать о движении вниз по стеку абстракции (машина, процесс, поток, аппаратные компоненты и т. д.). Большинство языков программирования используют поток как высший уровень абстракции. К счастью, Go дополняет это и представляет Горутин.

«Делитесь памятью, общаясь, а не общайтесь, делясь памятью». — один из девизов Go

Хотя Golang предоставляет традиционный механизм блокировки в пакете sync, его философия предпочитает «делиться памятью путем общения». Поэтому Golang представляет канал в качестве среды для горутин для связи друг с другом.

//goroutine
go func() { 
    // do some work
}()

//channel
dataStream := make(chan interface{})

Модель форк-соединения

Модель параллелизма fork-join, которой следует Go

Эта модель параллелизма используется в Golang. В любое время; дочерняя горутина может быть разветвлена для выполнения параллельной работы с родительской, а затем присоединяется обратно **в какой-то момент.

В каждой программе Go есть основная горутина. Главный может выйти раньше, чем его дочерние элементы; в результате необходима точка соединения, чтобы убедиться, что дочерняя горутина имеет шанс закончить.

Канал

Канал обладает следующими свойствами:

  • Горутина-безопасна (несколько горутин могут получить доступ к общему каналу без условий гонки)
  • Семантика очереди FIFO

Канал всегда возвращает 2 значения: 1 — возвращаемый объект, 1 — статус ( true означает действительный объект, false означает, что в этом канале больше не будут отправляться значения)

intStream := make(chan int)
close(intStream)
integer, ok := <- intStream 
fmt.Printf("(%v): %v", ok, integer) // (false): 0

Направление канала

var dataStream <-chan interface{} //read from only channel
var dataStream chan<- interface{} // write to only channel
var dataStream chan interface{} // 2 ways

Емкость канала

Емкость канала по умолчанию равна 0 (небуферизованный канал). Чтение из пустого или запись в полный канал блокирует.

c := make(chan int,10) //buffered size of 10
c := make(chan int) //unbuffered channel

если буферизованный канал пуст и имеет получателя, буфер будет пропущен и значениебудет передано непосредственно от отправителя к получателю.

Поведение канала против горутины

Когда горутина читает или записывает в канал, в зависимости от состояния канала может происходить различное поведение.

intStream := make(chan int) // 0 capacity channel

go func() {
    defer close(intStream) 
    for i:=1; i<=5; i++{
      intStream <- i
  }
}()

//range from a channel
for integer := range intStream { // always blocked until the channel is closed
    fmt.Printf("%v ", integer)
}

Поскольку поведение сложное, у нас должен быть способ сделать надежную и масштабируемую программу.

Надежный и масштабируемый способ работы с каналом

Вот 1 способ предложения:

  • Не более 1 Goroutine владеет каналом. Владение каналом должно быть небольшим, чтобы им можно было управлять.
  • Владелец канала имеет права записи (chan←);
  • В то время как потребитель имеет только просмотр только для чтения (←chan).
  1. Владельцы канала

Обязанности:

  • Инициальный канал
  • Пишите в канал или передайте право собственности другой горутине
  • Закрыть канал
  • Инкапсулировать и выставить канал как канал читателя

2. Канал потребитель

Обязанности:

  • Обработать, когда канал закрыт
  • Обработка блокирующего поведения при чтении из канала
chanOwner := func() <-chan int { 
    resultStream := make(chan int, 5) //init
    go func() {
        defer close(resultStream) // close
        for i:=0;i<=5;i++{ 
            resultStream <- i // write
    } }()
    return resultStream // read channel returned
}

resultStream := chanOwner()
for result := range resultStream {
  fmt.Printf("Received: %d\n", result)
}
fmt.Println("Done receiving!")

Ссылки:

Параллелизм в Go: инструменты и методы для разработчиков — Книга Кэтрин Кокс-Будей

Первоначально опубликовано на https://dev.to 26 мая 2022 г.