Когда следует использовать concurrency в golang?

Таким образом, помимо обработки нескольких запросов сервера есть ли другое время, когда concurrency имеет значение? Я спрашиваю, потому что это так встроено в то, что я чувствую расточительность, если не использую его, но я едва могу использовать его для этого.

Ответ 1

Вот хороший пример одного из изобретателей Go, Роб Пайка, использования concurrency, потому что это более простой способ выразить решение проблемы:

Лексическое сканирование в Go

Обобщая это немного, любая проблема производителя-потребителя является естественным образом подходит для 2 goroutines, используя канал для передачи выходов от производителя к потребителю.

Другое хорошее использование для concurrency взаимодействует с несколькими источниками ввода/вывода (диски, сеть, терминал и т.д.). Ваша программа должна быть в состоянии проснуться и выполнять некоторую работу, когда результат исходит из любого из этих источников. Это можно сделать с помощью одного потока и системного вызова типа poll (2) или select (2). Когда ваш поток просыпается, он должен выяснить, какой результат пришел, найти, где он остановился в соответствующей задаче, и подобрать оттуда. Это много кода, который вам нужно написать.

Написание этого кода намного проще с использованием одного goroutine для каждой задачи. Затем состояние этой задачи фиксируется неявно в goroutine, и сбор, где он остановился, прост, как просыпаться и работать.

Ответ 2

Не эксперт в Go (пока), но я бы сказал:

Всякий раз, когда это проще всего сделать.

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

Вам не нужно принимать специальные меры для того, чтобы несколько goroutines работали гармонично - они просто делают!

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

Просто использовать concurrency - на самом деле он даже не похож на concurrency - он выглядит почти процедурным.

/*
  Multiplex a number of channels into one.
*/
func Mux(channels []chan big.Int) chan big.Int {
    // Count down as each channel closes. When hits zero - close ch.
    var wg sync.WaitGroup
    wg.Add(len(channels))
    // The channel to output to.
    ch := make(chan big.Int, len(channels))

    // Make one go per channel.
    for _, c := range channels {
        go func(c <-chan big.Int) {
            // Pump it.
            for x := range c {
                ch <- x
            }
            // It closed.
            wg.Done()
        }(c)
    }
    // Close the channel when the pumping is finished.
    go func() {
        // Wait for everyone to be done.
        wg.Wait()
        // Close.
        close(ch)
    }()
    return ch
}

Единственной концессией, которую я должен сделать для concurrency, является использование sync.WaitGroup в качестве счетчика для параллельного подсчета.

Обратите внимание, что это не просто моя собственная работа - я очень помог с этим здесь.

Ответ 3

Также не эксперт в Go, поэтому некоторые из моих подходов могут быть неканоническими, но вот некоторые из способов, которые я нашел concurrency полезными до сих пор:

  • Выполнение операций в ожидании запроса сети, ввода-вывода на диске или запроса базы данных для завершения
  • Выполнение алгоритмов разделения и управления быстрее
  • Поскольку goroutines являются функциями, а функции являются первоклассными гражданами в Go, вы можете передавать их как переменные. Это удобно, когда ваша программа имеет множество автономных элементов. (Например, я играю с симуляцией городской системы движения. Каждое транспортное средство - это собственная горутин, и они общаются с перекрестками и другими транспортными средствами, используя каналы. Каждый делает свою собственную вещь.)
  • Одновременные операции ввода-вывода на разных устройствах
  • Используется concurrency для выполнения алгоритма Дейкстры на множестве точек изображения, чтобы нарисовать линии "интеллектуальных ножниц" - один горутин на точку сделал эту реализацию значительно быстрее.
  • GoConvey использует concurrency для одновременного запуска тестов по пакетам, чтобы быстрее реагировать на изменения при отладке с помощью веб-интерфейс. (В качестве присущего бонуса это добавляет некоторую псевдослучайность в последовательность тестирования, поэтому ваши результаты теста действительно соответствуют.)

Concurrency может быть (чтение: "иногда, но не обязательно всегда" ) полезно, когда у вас есть операции, которые могут выполняться независимо друг от друга, но в противном случае выполнялись бы последовательно. Даже если эти операции зависят от данных или какого-то сигнала от других goroutines в определенных точках, вы можете сообщить об этом всем с каналами.

Для некоторого вдохновения и важного различия в этих вопросах, а также для некоторых смешных изображений gopher см. Concurrency не Parallelism.

Ответ 4

Мои 2 цента... Если вы думаете о каналах /goroutines только в контексте concurrency, вам не хватает лодки.

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

Одним из основных принципов объектно-ориентированного проектирования является единоличная ответственность Principal. Применение этого принципа заставляет вас думать о дизайне с точки зрения сообщений, а не о сложном поведении объекта. Эти же конструктивные ограничения можно использовать в go, чтобы вы могли начать думать о "сообщениях на каналах", связывающих одноцелевые функции.

Это только один пример, но если вы начнете думать так, вы увидите еще много.