Распределите работу с помощью небольшого пула воркеров

GolangBeginner
Практиковаться сейчас

Введение

Проверки Garden Night уже правильно классифицируют ответы, но начальный вариант обрабатывает задания по одному. Медленная страница задерживает получение других полезных результатов. Вы распределите работу между небольшим фиксированным числом воркеров Go, сохранив результат каждого задания.

Этот самостоятельный раздел уже содержит готовую функцию проверки одной цели. Нужны знания функций, структур, срезов, модулей, базового синтаксиса каналов и HTTP-результатов из предыдущего раздела либо равноценный опыт. Горутины, закрытие каналов и завершение воркеров объясняются до использования. Запросы направляются только к подготовленным локальным целям; это разовый инструмент, не бенчмарк и не платформа мониторинга. Отведите около 60 минут с учётом чтения и обычных ошибок; это оценка автора.

Наблюдайте настоящую работу до её распределения

На этом шаге запустите текущую последовательную проверку и изучите фактические наблюдения службы. Начните в Workspace. cd выбирает проект, а export добавляет подготовленный каталог Go в путь поиска команд этой оболочки. Повторяйте обе строки в новом сеансе терминала.

cd /home/labex/project/site-checker
export PATH=/usr/local/go/bin:$PATH

Прочитайте функцию проверки, рабочий цикл и список без тайм-аутов. cat только показывает файлы:

cat check.go run.go overlap.json

Check уже возвращает ok, http_error, content_mismatch, connection_error или timeout, сохраняет идентичность задания и полученный статус, закрывает каждое тело. Последовательная функция Run игнорирует параметр workers. Это поведение предстоит изменить.

Каждый адрес в overlap.json выполняет конечную подготовленную работу длительностью 250ms. Используйте двухсекундный тайм-аут для каждой цели, чтобы изучать конкурентное выполнение, а не истечение времени клиента. go run . запускает пакет; -input выбирает задания, -workers задаёт максимум активных проверок, -timeout 2s устанавливает бюджет запроса. > записывает стандартный вывод в файл.

go run . -input overlap.json -workers 2 -timeout 2s > normal-report.json

Прочитайте его:

cat normal-report.json

Все шесть результатов должны быть ok. Сам отчёт не показывает, перекрывались ли проверки. Подготовленный наблюдатель записывает события enter и exit, когда настоящий обработчик сервера начинает и заканчивает работу; active считает обработчики, действительно занятые запросами. Небольшой Python-инструмент получает эти существующие записи, не создавая дополнительных запросов к целям:

python3 observe.py

В этой новой последовательной среде ожидается Actual handler peak: 1: входы и выходы чередуются, активен не более одного обработчика. Наблюдатель был готов до запросов. Конечная работа 250ms даёт конкурентным клиентам возможность перекрыться, но сама задержка или общее время не доказывают перекрытия. Доказательство — записи событий.

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

Передайте задания фиксированному числу воркеров

На этом шаге спроектируйте движение заданий и результатов, затем отредактируйте Run в Workspace. Сохраните завершённую Check и подготовленные входы без изменений. Ваш результат — работающий пул, а не просто несколько запущенных горутин.

Определите ответственность за каждый канал

Горутина — функция, выполняемая конкурентно с вызывающим кодом; go func() { ... }() немедленно запускает анонимную функцию. Её запуск не доказывает активность HTTP-запроса. Канал передаёт типизированные значения: make(chan Target) создаёт канал заданий, а make(chan Result) — канал результатов. В этих небуферизованных каналах jobs <- target отправляет значение и ждёт получателя. for target := range jobs получает значения до закрытия и опустошения канала. close(jobs) означает, что новых отправок не будет; уже полученные значения остаются действительными.

Перед редактированием определите, какая часть программы отправляет задания, получает задания, отправляет результаты, получает результаты и закрывает каждый канал. Должны быть один производитель заданий и один сборщик результатов. Воркеры получают задания и отправляют результаты. Только производитель знает, когда заданий для отправки больше нет, поэтому закрывает jobs. Воркер не может безопасно закрыть results, пока другой воркер ещё может отправить результат.

sync.WaitGroup отслеживает завершение. Объявление вроде var finished sync.WaitGroup начинает с нуля. Вызывайте finished.Add(1) до запуска каждого воркера; внутри него defer finished.Done() отмечает выход. finished.Wait() ждёт, пока каждый зарегистрированный воркер вызовет Done. Канал результатов можно закрывать только после этого ожидания. Передайте ожидание и закрытие отдельной горутине: если основная горутина ждёт до приёма, воркеры могут остаться заблокированными на отправках результатов. Производитель также работает отдельно, чтобы основная горутина могла принимать результаты во время распределения заданий.

Для запуска запрошенного числа воркеров счётный цикл вроде for i := 0; i < workers; i++ { ... } инициализирует i нулём, проверяет верхнюю границу перед каждой итерацией и увеличивает i после неё. Его тело каждый раз запускает одного воркера. Каждый воркер должен синхронно вызвать Check, отправить возвращённый результат и только затем получить следующее задание. Это ограничивает активные клиентские проверки числом воркеров. Не запускайте новую горутину для каждой цели внутри воркера. Разделяйте один http.Client с Timeout: budget; клиент поддерживает конкурентные запросы. Только сборщик добавляет в срез отчёта, поэтому воркеры не конкурируют за общий срез или map. Разные порядки завершения допустимы.

Для пустого списка целей производитель всё равно закрывает jobs. Затем воркеры заканчивают работу, горутина закрытия закрывает results, а сборщик возвращает инициализированный пустой срез. Проследите эту последовательность до написания кода. Также объясните, почему одна цель с тремя воркерами должна завершиться и почему несколько целей с одним воркером обрабатываются последовательно.

Отредактируйте один согласованный рабочий цикл

Создайте копию файла с расширением, отличным от .go, чтобы Go не компилировал второе определение Run. cp копирует файл, не меняя оригинал:

cp run.go run.go.bak

Откройте существующий файл:

nano run.go

Перемещайтесь стрелками, вставляйте текст вводом и удаляйте символы через Backspace или Delete. Ctrl означает удержание Control при нажатии другой клавиши. Ctrl+W, func Run, Enter ищет функцию; Ctrl+C отменяет поиск. Ctrl+K вырезает текущую строку, поэтому используйте его только для намеренного удаления этой строки. Сохраните через Ctrl+O, подтвердите run.go, затем Enter; Ctrl+X завершает редактор. Для отмены нежелательных изменений нажмите Ctrl+X, затем N, когда nano спросит о сохранении. В нежелательном запросе имени файла Ctrl+C отменяет запрос, позволяя исправить правку или сохранить под исходным именем.

Сохраните сигнатуру функции и существующий HTTP-клиент. Добавьте sync в группу импортов при использовании WaitGroup. Замените последовательный цикл целей своим проектом, выполняя обязанности в таком порядке:

Сначала создайте типизированные каналы заданий и результатов и WaitGroup завершения воркеров. Зарегистрируйте и запустите ровно workers воркеров. Каждый получает задания до закрытия jobs, отправляет каждый результат Check и отмечает завершение при возврате.

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

Также запустите отдельную горутину завершения, которая ждёт всех воркеров, затем закрывает results. Она не должна закрывать results, пока воркер ещё может отправлять.

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

Напишите все четыре части до запуска программы. Неполный пул без отправителя, закрывающей части или сборщика не является полезным промежуточным опытом. Вы решаете, как связать части; готовой Run для вставки нет. Имена локальных переменных и эквивалентные способы синхронизации не оцениваются.

Сохраните и выйдите, затем изучите настоящий файл. cat показывает его, не меняя:

cat run.go

gofmt -w форматирует исходник на месте. Синтаксическая ошибка указывает файл и строку: снова откройте run.go, исправьте участок, сохраните и повторите форматирование. Не перезаписывайте завершённые части новым ответом для всего файла.

gofmt -w run.go

Проверьте настоящее перекрытие работы

Теперь выполните полный пул на заданиях без тайм-аутов с двумя воркерами. Двухсекундный бюджет позволяет их конечной работе 250ms нормально завершиться:

go run . -input overlap.json -workers 2 -timeout 2s

Каждое задание должно вернуть ok. Прочитайте существующие записи наблюдателя:

python3 observe.py

В этой среде накопленная история теперь должна показать Actual handler peak: 2, с enter, пока другой обработчик ещё активен. Если пик остаётся равным одному, проверьте получение заданий и вызов Check воркерами; общая длительность не доказывает успех. Необычная задержка планирования может сделать наблюдение неопределённым. Когда последние записи заканчиваются exit и active=0, конечные запросы действительно завершены; можно повторить безопасный набор и прочитать его новые события.

Накопленный пик выше двух означает, что когда-то работа превысила лимит. Ранняя неверная реализация оставляет этот пик после исправления исходника. Прочитайте последние события входа и выхода вместе и используйте Verify, запускающую исходный код на независимой тестовой службе, для оценки текущей реализации. Не очищайте историю и не сбрасывайте VM для удаления прежнего пика.

Если команда зависла вместо завершения, Ctrl+C прерывает этот вызов на переднем плане; отдельно подготовленная служба мероприятия остаётся доступной. Снова откройте run.go и проверьте цепочку ответственности за каналы: отправлены ли все задания, закрыл ли производитель jobs, вызывают ли воркеры Done, может ли сборщик принимать во время отправок и закрывает ли горутина закрытия results в итоге? Исправьте локально и отформатируйте. Перед новым наблюдением конкурентного выполнения запустите python3 observe.py и подтвердите, что последнее записанное событие — exit с active=0. Если конечные запросы ещё активны, читайте снова до выполнения этого условия; не используйте угаданный sleep и не удаляйте наблюдения. Тайм-ауты клиента не останавливают серверную работу немедленно.

Если файл отсутствует, используйте pwd и cd первого шага, чтобы восстановить каталог. Если в новой оболочке нет go, повторите сеансовый export того шага. Ни обычная правка, ни ошибка пути не требуют изменения глобальных файлов оболочки или сброса VM.

Завершите каждое задание в пределах его бюджета

На этом шаге используйте пул воркеров для отчёта о мероприятии и проверьте граничные случаи. Прочитайте реальные задания перед запуском:

cat targets.json

Ожидаются два ok, один http_error со статусом 403, один content_mismatch с 200, один timeout с 0 и один connection_error с 0. Билетная служба задерживает ответ на конечные 600ms; клиентский бюджет 200ms намеренно завершает проверку раньше. Оба ID повторяющегося регистрационного URL должны остаться в отчёте.

Сохраните новый отчёт с двумя воркерами. > заменяет только report.json; ошибки по-прежнему видны в терминале.

go run . -input targets.json -workers 2 -timeout 200ms > report.json

Прочитайте действительно сохранённый файл:

cat report.json

Порядок строк может отличаться от входного списка и меняться между запусками. Сопоставляйте по ID: медленный запрос может закончиться после быстрого, назначенного позже. Один воркер не начинает следующую цель до возврата Check, но обработчик сервера может продолжать конечную работу после тайм-аута и ухода клиента. Поэтому курс измеряет лимит воркеров на отдельных запросах без тайм-аутов, а не считает продолжающие работу серверные обработчики ошибкой пула.

Пул должен также справляться с меньшим числом заданий, чем воркеров, и с отсутствием заданий. Эти команды используют подготовленные списки и выводят результат напрямую, не изменяя report.json:

go run . -input single.json -workers 3 -timeout 2s
go run . -input empty.json -workers 3 -timeout 2s

Ожидайте один ok, затем []; обе команды должны завершиться. Наконец задайте лимит один для нескольких целей без тайм-аута:

go run . -input overlap.json -workers 1 -timeout 2s

Все результаты остаются ok. Этот запуск должен быть последовательным, даже если код умеет запускать несколько воркеров. Накопленный максимум наблюдателя хранит прежнюю активность и не позволяет выделить лимит одного запуска. Проверка курса создаёт отдельную новую службу для каждого бюджета, включая один, два и три. Для нескольких целей при бюджете два или три требуется реальное перекрытие минимум двух запросов без превышения заданного максимума; ровно три на пике не требуются. У пустых наборов и одиночных целей требования перекрытия нет.

Сохраните полный отчёт, исходные входные данные и службу. Теперь у вас есть повторно используемая команда, которая ограничивает активную работу и выдаёт все результаты, включая честно описанные неудачи.

Резюме

Вы использовали каналы заданий и результатов с явной ответственностью за закрытие, фиксированное число воркеров и синхронизацию завершения. Один сборщик сохраняет все результаты без конкурентных изменений общего среза. Настоящие события сервера отличают последовательную работу, ограниченное конкурентное выполнение и превышение лимита; отдельные тесты тайм-аутов не путают продолжающуюся серверную работу с нарушением лимита воркеров. Отчёт полезен даже при отказе некоторых страниц.