Увеличьте «мощность» обработки Golang с помощью контролируемого рабочего пула
Это будет краткое описание, если вы не использовали возможности go-рутины, чтобы сделать процесс вашего приложения «быстрее». В этом примере мы сделаем простую симуляцию выполнения функции, которая имитирует «медленный процесс», выполнение которого занимает 1 секунду, и мы собираемся вызвать эту функцию несколько раз с другим параметром (для имитации другого ввода / обработки).
Синхронный процесс
Давайте напишем простую функцию, которая имитирует процесс, который занимает 1 секунду, примерно так.
func DoSomethingHeavy(id int) {
time.Sleep(1 * time.Second)
// you can print the id of do anything with it, it just simulate an identifier of which data to process
}
Затем создадим функцию, которая будет выполнять 10 итераций, в каждой из которых мы вызовем DoSomethingHeavy
.
func snycProcess() {
start := time.Now()
for x := 0; x < 10; x++ {
DoSomethingHeavy(x)
}
fmt.Println("SYNCPROCESS ELAPSED UNTIL ALL DONE", time.Since(start))
}
Запустите его несколько раз, и, как и ожидалось, вы увидите, что время в журнале ELAPSED составляет 10 секунд. Вот мой результат:
SYNCPROCESS ELAPSED UNTIL ALL DONE 10.022615553s
Это было ожидаемо, так как каждый вызов будет задерживаться на 1 секунду, а он запускался 10 раз. Но давайте сделаем лучше, используя горутину в простейшей форме.
Параллелизм с использованием подпрограммы Go
Вместо того, чтобы вызывать DoSomethingHeavy
как есть, мы собираемся превратить его в под процесс.
func concurrentProcessGofunc() {
start := time.Now()
var wg sync.WaitGroup
wg.Add(10) // 10 concurrent process
for x := 0; x < 10; x++ {
index := x
go func() {
DoSomethingHeavy(index)
wg.Done()
}()
}
wg.Wait() // wait until all 10 process is done
fmt.Println("concurrentProcess ELAPSED UNTIL ALL DONE", time.Since(start))
}
Вызов функции concurrentProcessGoFunc
будет быстрее отрабатывать:
concurrentProcess ELAPSED UNTIL ALL DONE 1.002388768s
Это связано с тем, что все 10 процессов выполняются одновременно, а общее прошедшее время равно МАКС (timeProcess1, timeProcess2, timeProcess3,…, timeProcess10). Однако эта простая форма «go func () {…. } ()» не рекомендуется, особенно если вы собираетесь обрабатывать что-то с «неизвестным» размером. Под неизвестным я имею в виду ввод от пользователя, список данных из базы данных и т.д. Этот код покажет вам потенциальную проблему.
comments := user.Comments
// be that comments coming in from user request or database, the "size" is unknown, and there is a chance it will be huge for some usecase / user maybe
for _, c := range comments {
go func(userComment Comment) {
// basically handling the copy of object (c) concurrently
DoSomethingWith(userComment)
}(c)
}
Приведенный выше код может иметь потенциальную проблему исчерпания вашего ресурса, если комментариев много, и есть много людей, запускающих всю функцию, другими словами, вы можете создать «слишком много» процедур go.
Параллелизм с ограничением
Есть еще один способ использовать процедуру go для ее одновременной обработки, НО все еще ограничивая ее, чтобы предотвратить, возможное истощение системы. Мы можем использовать эту следующую библиотеку пула подпрограмм Go с ограничением параллелизма.
Concurrency limiting goroutine pool. Contribute to gammazero/workerpool development by creating an account on GitHub.
В примере создан пул с желаемым количеством «воркеров», и вы можете поставить в очередь любую задачу в пул. Задача будет обработана воркером, если он не занят и есть воркер, готовый обработать его. Короче говоря, мы можем ограничить количество процедур worker / go, но также по-прежнему использовать процесс параллелизма с этой библиотекой. Это пример кода с регистрацией прошедшего времени.
func concurrentProcessWorkerPool() {
wp := workerpool.New(10) // create 10 worker
start := time.Now()
for x := 0; x < 10; x++ {
index := x
wp.Submit(func() {
DoSomethingHeavy(index)
}) // submit task to worker
}
wp.StopWait() // stop and wait until all queue tasks are done
fmt.Println("concurrentProcessWorkerPool ELAPSED UNTIL ALL DONE", time.Since(start))
}
Используя эту технику, мы получаем
concurrentProcessWorkerPool ELAPSED UNTIL ALL DONE 1.00305283s
Тот же результат с простой формой go func
, но в данном случае мы можем ограничить количество порождаемых подпрограмм go. Если мы позволим, скажем, изменить номер воркера в инициализации (workerpool.New) на 2, мы получим истекшее время в 5 секунд. Это связано с тем, что для каждого пакета только 2 параллельных процесса, а у нас есть 5 пакетов для обработки (10 задач разделены на 2 рабочих).
concurrentProcessWorkerPool ELAPSED UNTIL ALL DONE 5.00946675s
Поскольку количество воркеров уменьшилось с 10 до 2, мы получаем более большее время для обработки всего этого, но также и меньшее использование ресурсов. Оптимальный компромисс между скоростью / мощностью - ресурсом может варьироваться в зависимости от вашего реального производственного случая.
Резюме
Вы можете улучшить свое приложение, чтобы использовать процедуру golang для одновременной обработки множества вещей. Помните, что количество подпрограмм go, созданных в вашем приложении, влияет на ресурсы, поэтому, если вы хотите «сохранить его предел» из соображений безопасности, эта библиотека может вам в этом помочь. Имейте в виду, что в библиотеке указано, что:
Нет верхнего предела на количество задач в очереди, кроме пределов системных ресурсов