понедельник, 22 ноября 2021 г.

Go для Java разработчиков: горутины и каналы

Горутины

Go позволяет запустить новый поток выполнения, горутину (goroutine), с помощью оператора go. Он запускает функцию в другой, недавно созданной горутине. Все горутины в одной программе используют одно и то же адресное пространство.

Горутины легкие и стоят немного больше, чем выделение места в стеке. Стеки начинаются с малого и увеличиваются за счет выделения и освобождения кучи по мере необходимости. Внутренне горутины действуют как сопрограммы (coroutines), которые мультиплексируются между несколькими потоками операционной системы.

go list.Sort() // Выполнить list.Sort параллельно.

В Go есть литералы функций, которые могут действовать как замыкания и эффективны в сочетании с оператором go.

// Publish печатает текст на стандартный вывод 
// по истечении заданного времени.
func Publish(text string, delay time.Duration) {
    go func() {
        time.Sleep(delay)
        fmt.Println(text)
    }() // Обратите внимание на круглые скобки. 
        // Мы должны вызвать функцию.
}

Переменные text и delay распределяются между окружающей функцией и литералом функции; они выживают, пока доступны.

Каналы

Канал предоставляет механизм для двух горутин для синхронизации выполнения и обмена данными, передавая значение определенного типа элемента. Оператор <- указывает направление канала, отправка или получение. Если направление не указано, канал двунаправленный.

chan Sushi     // может использоваться для отправки и получения значений типа Sushi
chan<- float64 // может использоваться только для отправки float64
<-chan int     // можно использовать только для получения целых чисел

Каналы являются ссылочным типом и назначаются с помощью make.

ic := make(chan int)       // небуферизованный канал целых чисел
wc := make(chan *Work, 10) // буферизованный канал указателей на Work

Чтобы отправить значение по каналу, используйте <- в качестве бинарного оператора. Чтобы получить значение по каналу, используйте его как унарный оператор.

ic <- 3      // Посылаем 3 по каналу.
work := <-wc // Получение указателя на Work из канала.

  • Если канал не буферизован, отправитель блокируется до тех пор, пока получатель не получит значение.
  • Если канал имеет буфер, отправитель блокируется только до тех пор, пока значение не будет скопировано в буфер; если буфер заполнен, это означает ожидание, пока какой-либо получатель не получит значение.
  • Получатели блокируются до тех пор, пока не появятся данные для приема.

Функция close записывает, что по каналу больше не будут отправляться значения:

  • После вызова close и после получения любых ранее отправленных значений операции приема вернут нулевое значение без блокировки.
  • Операция приема с несколькими значениями дополнительно возвращает указание, закрыт ли канал.

ch := make(chan string)
go func() {
    ch <- "Hello!"
    close(ch)
}()
fmt.Println(<-ch) // Печатает "Hello!".
fmt.Println(<-ch) // Распечатать нулевое значение "" без блокировки.
fmt.Println(<-ch) // Еще раз печатаем "".
v, ok := <-ch     // v - "", ok - false.

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

// Publish печатает текст на стандартный вывод по истечении заданного времени.
// Закрывает канал wait, когда текст был опубликован.
func Publish(text string, delay time.Duration) (wait <-chan struct{}) {
    ch := make(chan struct{})
    go func() {
        time.Sleep(delay)
        fmt.Println(text)
        close(ch)
    }()
    return ch
}

Вот как вы можете использовать эту Publish функцию.

wait := Publish("важные новости", 2 * time.Minute)
// Проделаем еще немного работы.
<-wait // блокируется, пока текст не будет опубликован

Оператор select

Оператор select - последний инструмент в наборе инструментов конкурентности Go. Он выбирает, какая из возможных коммуникаций будет продолжена:

  • если какой-либо из case может продолжаться, один из них выбирается случайным образом и выполняются соответствующие операторы;
  • в противном случае, если нет default case, инструкция блокируется до тех пор, пока не будет завершен один из case.

Вот простой пример, показывающий, как можно использовать оператор select для реализации генератора случайных чисел.

rand := make(chan int)
for { // Посылаем случайную последовательность битов в rand.
    select {
    case rand <- 0: // примечание: нет инструкции
    case rand <- 1:
    }
}

Более реалистично, вот как можно использовать оператор select для установки ограничения по времени для операции приема.

select {
case news := <-AFP:
    fmt.Println(news)
case <-time.After(time.Minute):
    fmt.Println(Тайм-аут: нет новостей в течение одной минуты.)
}

Функция time.After является частью стандартной библиотеки; она ожидает истечения указанного времени, а затем отправляет текущее время по возвращенному каналу.


Читайте также:


Купить gopher

Комментариев нет:

Отправить комментарий