Показаны сообщения с ярлыком каналы в Go. Показать все сообщения
Показаны сообщения с ярлыком каналы в Go. Показать все сообщения

понедельник, 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

среда, 6 октября 2021 г.

Ограничение использования ресурсов для программы в Golang

Чтобы ограничить использование программой ресурса, такого как память, пусть горутины синхронизируют использование этого ресурса с помощью буферизованного канала (т. е. используют канал как семафор):

const (
    AvailableMemory         = 10 << 20 // 10 MB
    AverageMemoryPerRequest = 10 << 10 // 10 KB
    MaxOutstanding          = AvailableMemory / AverageMemoryPerRequest
)

var sem = make(chan int, MaxOutstanding)

func Serve(queue chan *Request) {
    for {
        sem <- 1 // Блокировать до тех пор, 
                 // пока не появится возможность обработать запрос.
        req := <-queue
        go handle(req) // Не дожидаемся завершения handle.
    }
}

func handle(r *Request) {
    process(r) // Может занять много времени 
               // и потреблять много памяти или ЦП
    <-sem      // Готово; разрешить запуск следующего запроса.
}


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


воскресенье, 8 ноября 2020 г.

Go style guides: размер канала - один или нет

Каналы обычно должны иметь размер один или быть небуферизованными. По умолчанию каналы не буферизованы и имеют нулевой размер. Любой другой размер должен подвергаться тщательной проверке. Подумайте, как определяется размер, что предотвращает заполнение канала под нагрузкой и блокировку писателей и что происходит, когда это происходит.

Спорный вариант:

// Должно хватить на кого угодно!
c := make(chan int, 64)

Хороший вариант:

// Размер один
c := make(chan int, 1) // или
// Небуферизованный канал, нулевой размер
c := make(chan int)


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


пятница, 29 мая 2020 г.

Использовать sync.Mutex или канал?

Один из девизов Go: "Не общайтесь, разделяя память; делитесь памятью, общаясь".

Тем не менее, Go предоставляет традиционные механизмы блокировки в пакете sync. Большинство проблем с блокировкой можно решить с помощью каналов или традиционных блокировок.

Так что вы должны использовать?

Используйте то, что наиболее выразительно и/или наиболее просто.

Распространенной ошибкой новичка в Go является чрезмерное использование каналов и goroutine только потому, что это возможно, и/или потому, что это весело. Не бойтесь использовать sync.Mutex, если это лучше всего подходит для вашей проблемы. Go прагматичен, позволяя вам использовать инструменты, которые наилучшим образом решают вашу проблему, и не заставляет вас использовать один стиль кода.

Как общее руководство, однако:

Канал Мьютекс
передача права собственности на данные, раздача единиц работы, передача асинхронных результатов кэши, состояние

Если вы когда-нибудь обнаружите, что правила блокировки sync.Mutex становятся слишком сложными, спросите себя, может ли быть проще использовать канал(ы).

Группа ожидания (sync.WaitGroup)

Другим важным примитивом синхронизации является sync.WaitGroup. Он позволяет взаимодействующим группам совместно ожидать пороговое событие, прежде чем продолжить самостоятельно. Это полезно, как правило, в двух случаях.

Во-первых, при "очистке" можно использовать sync.WaitGroup, чтобы гарантировать, что все goroutines, включая основную (main), ожидают, пока все не завершатся чисто.

Второй более общий случай - это циклический алгоритм, который включает набор процедур, которые некоторое время работают независимо, затем все ждут на барьере, прежде чем продолжить самостоятельно. Этот шаблон может повторяться много раз. Данные могут быть обменены на барьерном событии. Эта стратегия является основой Массового Синхронного Параллелизма (BSP, Bulk Synchronous Parallelism).

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


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


среда, 25 марта 2020 г.

Передача сигнала по каналу в Golang

Все читатели получают нулевые значения на закрытом канале.

В этом примере функция 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
}

Обратите внимание, что мы используем канал пустых структур: struct{}. Это ясно указывает на то, что канал будет использоваться только для сигнализации, а не для передачи данных.

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

func main() {
    wait := Publish("Каналы позволяют goroutines общаться.", 5*time.Second)
    fmt.Println("Ожидание новостей...")
    <-wait
    fmt.Println("Время выходить.")
}

Вывод:

Ожидание новостей...
НОВОСТИ: Каналы позволяют goroutines общаться.
Время выходить.


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


Купить gopher

понедельник, 16 марта 2020 г.

Каналы в Golang предлагают синхронизированное общение

Канал - это механизм, позволяющий goroutines синхронизировать выполнение и обмениваться данными посредством передачи значений.

Новое значение канала можно задать с помощью встроенной функции make.

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

// буферизованный канал с местом для 10 строк
sc := make(chan string, 10)

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

ic <- 3   // Отправляем 3 по каналу
n := <-sc // Получаем string из канала

Оператор <- указывает направление канала, отправлять или получать. Если направление не указано, канал является двунаправленным.

chan Sushi    // может использоваться для отправки 
              // и получения значений типа Sushi

chan<- string // может использоваться только 
              // для отправки строк

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

Буферизованные и небуферизованные каналы

Если пропускная способность канала равна нулю или отсутствует, канал не буферизуется, и отправитель блокируется, пока получатель не получит значение.

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

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

Отправка или получение из нулевого канала блокирует навсегда.

Закрытие канала

Функция 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.

// Получаем значения от ch до закрытия.
for v := range ch {
    fmt.Println(v) // Не будет выполнено.
}

Пример

В следующем примере мы позволяем функции 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
}

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

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


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


Купить gopher

воскресенье, 16 июня 2019 г.

Спецификация Go: утверждения отправки (send statements)

Утверждение отправки отправляет значение по каналу. Выражение канала должно быть типа канала, направление канала должно разрешать операции отправки, а тип отправляемого значения должен быть назначен типу элемента канала.

SendStmt = Channel "<-" Expression .
Channel  = Expression .

И канал, и выражение значения оцениваются до начала связи. Связь блокируется до тех пор пока отправка не может продолжаться. Отправка по небуферизованному каналу может продолжаться, если получатель готов. Отправка по буферизованному каналу может продолжаться, если в буфере есть место. Отправка по закрытому каналу продолжается, вызывая панику во время выполнения. Отправка по нулевому каналу (nil channel) блокирует навсегда.

ch <- 3  // отправить значение 3 в канал ch


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


среда, 29 мая 2019 г.

Спецификация Go: типы каналов (channel types)

Канал (channel) предоставляет одновременно выполняемым функциям механизм для обмена данными путем отправки и получения значений определенного типа элемента. Значение не инициализированного канала равно nil.

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

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

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

Оператор <- ассоциируется с самым левым возможным chan:

chan<- chan int    // то же самое что и chan<- (chan int)
chan<- <-chan int  // то же самое что и chan<- (<-chan int)
<-chan <-chan int  // то же самое что и <-chan (<-chan int)
chan (<-chan int)

Новое, инициализированное значение канала может быть создано с использованием встроенной функции make, которая принимает тип канала и необязательную емкость в качестве аргументов:

make(chan int, 100)

Емкость, в количестве элементов, устанавливает размер буфера в канале. Если пропускная способность равна нулю или отсутствует, канал не буферизуется, и связь устанавливается успешно только тогда, когда отправитель и получатель готовы. В противном случае канал буферизуется, и связь успешно завершается без блокировки, если буфер не заполнен (отправляет) или не пуст (получает). nil канал никогда не готов к общению.

Канал может быть закрыт с помощью встроенной функции close. Многозначная форма назначения оператора получения сообщает о том, было ли полученное значение отправлено до закрытия канала.

Один канал может использоваться в операторах отправки, операциях приема и вызовах встроенных функций cap и len любым количеством процедур без дальнейшей синхронизации. Каналы действуют как очереди «первым пришел - первым обслужен» (first-in-first-out, FIFO). Например, если одна программа отправляет значения по каналу, а вторая программа получает их, значения принимаются в порядке отправки.


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