Пост Go’s WaitGroup == JavaScript’s PromiseAll?? впервые появился на Qvault.

В приложениях, интенсивно использующих ввод-вывод, синхронное выполнение функций с высокой задержкой одна за другой может стать неуклюжим. Например, если у меня есть веб-страница, которой нужно запросить 7 файлов с сервера, прежде чем она сможет отобразить страницу, мне нужно асинхронно получить все эти файлы одновременно. Альтернатива займет слишком много времени. Здесь на помощь приходят PromiseAll и WaitGroup.

Давайте рассмотрим пример синхронного* кода JavaScript:

const fetch = require('node-fetch')

async function runSync() {
    const resp = await fetch('https://qvault.io')
    let text = await resp.text()
    console.log(text)
    const resp2 = await fetch('https://github.com')
    text = await resp2.text()
    console.log(text)
    const resp3 = await fetch('https://gitlab.io')
    text = await resp3.text()
    console.log(text)
}

runSync()
*Note: Due to some technicalities with JavaScript, the above utilizes asynchronous code (see async/await), but for the purposes of our discussion, each fetch() is synchronous in relation to each other.

Чтобы ускорить это, мы хотим, чтобы каждый сетевой вызов сервера (вызовы fetch()) происходил в одно и то же время. Посмотри:

const fetch = require('node-fetch')

async function runAsync() {
    const promise1 = await fetch('https://qvault.io')
    const promise2 = await fetch('https://github.com')
    const promise3 = await fetch('https://gitlab.io')

    await Promise.all([promise1, promise2, promise3]).then(async (values) => {
        let text = await values[0].text()
        console.log(text)
        text = await values[1].text()
        console.log(text)
        text = await values[2].text()
        console.log(text)
    });
}

runAsync()

группа ожидания

В Go у нас похожая концепция, стандартный тип пакета синхронизации WaitGroup. Однако сначала давайте посмотрим, как синхронно получать данные по сети:

package main

import (
	"bytes"
	"fmt"
	"net/http"
)

func main() {
	getAndPrintData("https://qvault.io")
	getAndPrintData("https://github.com")
	getAndPrintData("https://gitlab.io")
}

func getAndPrintData(url string) {
	resp, _ := http.Get(url)
	buf := new(bytes.Buffer)
	buf.ReadFrom(resp.Body)
	fmt.Println(buf.String())
}

Как и раньше, проблема здесь в том, что каждый сетевой вызов выполняется последовательно, что приводит к потере времени. Давайте воспользуемся горутинами, которые мы начнем с ключевого слова go:

package main

import (
	"bytes"
	"fmt"
	"net/http"
)

func main() {
	go getAndPrintData("https://qvault.io")
	go getAndPrintData("https://github.com")
	go getAndPrintData("https://gitlab.io")
}

func getAndPrintData(url string) {
	resp, _ := http.Get(url)
	buf := new(bytes.Buffer)
	buf.ReadFrom(resp.Body)
	fmt.Println(buf.String())
}

Если вы запустите этот код, вы увидите, что ничего не печатается и программа завершает работу почти сразу. проблема в том, что после запуска 3 отдельных горутин и возврата выполнения основному потоку ничто не мешает main() выйти. Как только main выходит, он очищает свои горутины, прежде чем они смогут получить ответ.

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

package main

import (
	"bytes"
	"fmt"
	"net/http"
	"sync"
)

func main() {
	wg := sync.WaitGroup{}
	wg.Add(3)
	go getAndPrintData("https://qvault.io", &wg)
	go getAndPrintData("https://github.com", &wg)
	go getAndPrintData("https://gitlab.io", &wg)
	wg.Wait()
}

func getAndPrintData(url string, wg *sync.WaitGroup) {
	defer wg.Done()
	resp, _ := http.Get(url)
	buf := new(bytes.Buffer)
	buf.ReadFrom(resp.Body)
	fmt.Println(buf.String())
}

Сначала мы создаем группу ожидания, в нашем случае wg. Затем мы используем функцию Add(), чтобы сообщить группе ожидания, что есть 3 счетчика для ожидания. Мы передаем указатель на группу ожидания каждой горутине и используем ключевое слово defer, чтобы пометить счетчик выполненным при выходе из каждой горутины.

В основном потоке мы используем функцию Wait(), чтобы заблокировать основной поток, пока все горутины не завершатся.

Группы ожидания в Go очень похожи на PromiseAll в JavaScript и могут быть полезным инструментом при разработке клиентских веб-приложений.

Спасибо за чтение

Напишите мне в твиттере @wagslane, если у вас есть какие-либо вопросы или комментарии.

Следите за мной на Dev.to: wagslane

Пост Go’s WaitGroup == JavaScript’s PromiseAll?? впервые появился на Qvault.