Introducción
Las comprobaciones de Garden Night ya clasifican correctamente las respuestas, pero el primer borrador procesa las asignaciones de una en una. Una entrada lenta retrasa la llegada de otros resultados útiles. Repartirás las tareas entre un número pequeño y fijo de workers de Go sin perder ningún resultado asignado.
Esta unidad independiente incluye una función completa para comprobar un destino. Debes conocer funciones, estructuras, slices, módulos, sintaxis básica de canales y los resultados HTTP de la unidad anterior, o tener experiencia equivalente. Las goroutines, el cierre de canales y la finalización de workers se explican antes de usarlos. Solo se solicitan destinos locales preparados; es una herramienta puntual, no una plataforma de pruebas de rendimiento o monitorización. Calcula unos 60 minutos, incluida la lectura y los errores habituales; es una estimación del autor.
Observa el trabajo real antes de repartirlo
En este paso, ejecutarás el comprobador secuencial actual y examinarás observaciones reales del servicio. Empieza en Workspace. cd selecciona el proyecto; export añade el directorio Go preparado a la ruta de búsqueda de comandos de esta shell. Repite ambas líneas en una nueva sesión.
cd /home/labex/project/site-checker
export PATH=/usr/local/go/bin:$PATH
Lee la función de comprobación, el bucle de trabajo y la lista sin tiempos de espera agotados. cat solo muestra los archivos:
cat check.go run.go overlap.json
Check ya devuelve ok, http_error, content_mismatch, connection_error o timeout, conserva la identidad y el estado recibido, y cierra cada cuerpo. El Run secuencial ignora el parámetro workers. Ese es el comportamiento que cambiarás.
Cada entrada de overlap.json realiza un trabajo preparado y finito de 250ms. Usa dos segundos de tiempo máximo por destino para observar concurrencia, no vencimientos del cliente. go run . ejecuta este paquete; -input selecciona las asignaciones, -workers pide un máximo de comprobaciones activas y -timeout 2s fija el presupuesto. > escribe la salida estándar en un archivo.
go run . -input overlap.json -workers 2 -timeout 2s > normal-report.json
Léelo:
cat normal-report.json
Las seis comprobaciones deben ser ok. El informe por sí solo no demuestra si se solaparon. El observador preparado registra eventos enter y exit cuando un manejador real del servidor empieza y termina; active cuenta los manejadores que están procesando peticiones. El pequeño lector Python consulta estas observaciones existentes sin añadir peticiones a los destinos:
python3 observe.py
En esta escena secuencial nueva, espera Actual handler peak: 1; las entradas y salidas alternan y nunca hay más de un manejador activo. El observador estaba listo antes de las peticiones. El trabajo finito de 250ms ofrece a los clientes concurrentes la oportunidad de solaparse; ni un retraso ni la duración total demuestran por sí solos ese solapamiento. La evidencia está en el registro.
El borrador ya produce un informe completo, pero su máximo de trabajo activo sigue siendo uno. Conserva ese informe como muestra normal de este paso. El siguiente sustituye solo run.go; el servicio, las definiciones de destinos y la función de comprobación siguen disponibles.
Asigna tareas a un número fijo de workers
En este paso, diseña cómo se mueven las tareas y los resultados y después edita Run en Workspace. Conserva sin cambios el Check completo y las entradas preparadas. Tu resultado es un grupo funcional, no solo varias goroutines iniciadas.
Decide quién tiene la responsabilidad de cada canal
Una goroutine es una función que se ejecuta concurrentemente con quien la llama; go func() { ... }() inicia inmediatamente una función anónima. Iniciarla no demuestra que haya una petición HTTP activa. Un canal transfiere valores de un tipo: make(chan Target) crea un canal de tareas y make(chan Result) crea un canal de resultados. En estos canales sin búfer, jobs <- target envía un valor y espera a un receptor. for target := range jobs recibe hasta que el canal esté cerrado y se hayan consumido sus valores pendientes. close(jobs) significa que no se enviarán más valores; los ya recibidos siguen siendo válidos.
Antes de editar, decide qué parte de tu programa enviará tareas, recibirá tareas, enviará resultados, recibirá resultados y cerrará cada canal. Debe haber un productor de tareas y un recolector de resultados. Los workers reciben tareas y envían resultados. Solo el productor sabe cuándo ya no se enviarán tareas, así que cierra jobs. Un worker no puede cerrar results con seguridad mientras otro aún pueda enviar.
sync.WaitGroup sigue la finalización. Una declaración como var finished sync.WaitGroup empieza en cero. Llama a finished.Add(1) antes de iniciar cada worker; dentro de ese worker, defer finished.Done() indica su salida. finished.Wait() espera hasta que todos los workers registrados hayan llamado a Done. El canal de resultados solo puede cerrarse cuando termine esa espera. Asigna esa responsabilidad de esperar y cerrar a otra goroutine: si la goroutine principal esperara antes de recibir, los workers podrían quedar bloqueados enviando resultados. El productor también se ejecuta por separado para que la goroutine principal pueda recibir resultados mientras se asignan tareas.
Para iniciar el número solicitado de workers, un bucle contador como for i := 0; i < workers; i++ { ... } inicializa i a cero, comprueba el límite superior antes de cada iteración e incrementa i después. Su cuerpo inicia un worker cada vez. Cada worker debe llamar a Check de forma síncrona, enviar el resultado devuelto y solo después recibir otra tarea. Eso limita las comprobaciones activas del cliente al número de workers. No inicies una goroutine nueva por destino dentro del worker. Comparte un http.Client con Timeout: budget; ese cliente admite peticiones concurrentes. Solo el recolector añade al slice del informe, así que los workers no compiten por modificar un slice o map compartido. Distintos órdenes de finalización son válidos.
Para una lista vacía de destinos, el productor sigue cerrando jobs. Los workers terminan, la goroutine de cierre cierra results y el recolector puede devolver un slice vacío inicializado. Sigue esta secuencia antes de escribir código. Explica también por qué un destino con tres workers debe terminar y por qué varios destinos con un worker deben procesarse secuencialmente.
Edita un bucle de trabajo coherente
Haz una copia con una extensión distinta de .go para que Go no compile una segunda definición de Run. cp copia el archivo sin cambiar el original:
cp run.go run.go.bak
Abre el archivo existente:
nano run.go
Muévete con las flechas, escribe para insertar texto y usa Backspace o Delete para eliminar caracteres. Ctrl significa mantener Control mientras pulsas la otra tecla. Ctrl+W, func Run, Enter busca la función; Ctrl+C cancela una búsqueda. Ctrl+K corta la línea actual, así que úsalo solo cuando quieras eliminar esa línea. Guarda con Ctrl+O, confirma run.go y pulsa Enter; Ctrl+X sale. Para descartar cambios no deseados, pulsa Ctrl+X y después N cuando nano pregunte si quieres guardar. Ante una pregunta con un nombre de archivo no deseado, Ctrl+C la cancela para que puedas corregir la edición o guardar con el nombre original.
Conserva la firma de función y el cliente HTTP existente. Añade sync al grupo de importaciones cuando uses su WaitGroup. Sustituye el bucle secuencial de destinos por tu diseño, trabajando en estas responsabilidades por orden:
Primero crea canales tipados de tareas y resultados y un WaitGroup de finalización de workers. Registra e inicia exactamente workers workers. Cada worker recibe hasta que jobs se cierra, envía cada resultado de Check e indica su finalización al regresar.
Después inicia un productor separado que envíe cada destino, incluidas las URL repetidas con ID distintos. Cierra jobs después del último envío, también cuando no haya destinos.
Inicia además una goroutine de finalización separada que espere a todos los workers y cierre después results. No debe cerrar results mientras algún worker aún pueda enviar.
Por último, en la goroutine principal, inicializa un slice de resultados vacío, recibe cada resultado hasta que se cierre el canal de resultados, añade cada uno y devuelve el slice.
Escribe las cuatro partes antes de ejecutar el programa. Un grupo incompleto sin emisor, cierre o recolector no es un experimento intermedio útil. Estás decidiendo cómo se conectan las partes; no hay un Run terminado suministrado para pegar. No se evalúan los nombres de tus variables locales ni las opciones equivalentes de sincronización.
Guarda y sal; después inspecciona tu archivo real. cat lo muestra sin modificarlo:
cat run.go
gofmt -w da formato al código en el mismo archivo. Un error de sintaxis identifica archivo y línea: vuelve a abrir run.go, repara esa zona, guarda y repite el formato. No sobrescribas las partes completadas con una respuesta nueva de todo el archivo.
gofmt -w run.go
Comprueba trabajo real solapado
Ejecuta ahora el grupo completo con las asignaciones que no agotan el tiempo y dos workers. El presupuesto de dos segundos permite que su trabajo finito de 250ms termine normalmente:
go run . -input overlap.json -workers 2 -timeout 2s
Cada asignación debe devolver un resultado ok. Lee los registros existentes del observador:
python3 observe.py
En este escenario, el registro acumulado debe mostrar ahora Actual handler peak: 2, con un enter mientras otro manejador siga activo. Si el máximo permanece en uno, inspecciona cómo reciben los workers y llaman a Check; el tiempo transcurrido no demuestra éxito. Una demora inusual del planificador puede dejar una observación inconclusa. Cuando el registro más reciente termina con exit y active=0, las peticiones finitas han terminado realmente; puedes repetir el lote inocuo y leer sus nuevos eventos.
Un máximo acumulado superior a dos indica que en algún momento se superó el límite. Una implementación anterior incorrecta deja ese máximo incluso después de corregir el código. Lee juntos los últimos eventos de entrada/salida y usa Verify, que ejecuta tu código original en un escenario independiente, para evaluar la implementación actual. No borres el historial ni reinicies la VM para eliminar un máximo anterior.
Si el comando sigue bloqueado en vez de regresar, Ctrl+C interrumpe esta invocación en primer plano; el servicio del evento preparado por separado sigue disponible. Vuelve a abrir run.go y comprueba la cadena de responsabilidades de canales: ¿se enviaron todas las tareas, cerró jobs el productor, llaman los workers a Done, puede recibir el recolector mientras envían y termina la goroutine de cierre cerrando results? Repara localmente y da formato. Antes de iniciar otra observación de concurrencia, ejecuta python3 observe.py y confirma que su último evento registrado sea un exit con active=0. Si aún hay peticiones finitas activas, vuelve a leer hasta que se cumpla la condición registrada; no uses una espera adivinada ni elimines observaciones. Los vencimientos del cliente no detienen inmediatamente el trabajo del servidor.
Si falta un archivo, usa pwd y el cd del primer paso para recuperar el directorio. Si go no está disponible en una shell nueva, repite el export de sesión de ese paso. Ni una edición corriente ni un error de ruta requieren cambiar archivos globales de la shell o reiniciar la VM.
Termina cada asignación dentro de su presupuesto
En este paso, usarás el grupo de workers para el informe del evento y probarás sus límites. Lee las asignaciones reales antes de ejecutarlas:
cat targets.json
Los tipos esperados son dos ok, un http_error con estado 403, un content_mismatch con 200, un timeout con cero y un connection_error con cero. El servicio de entradas tiene una demora finita de 600ms; el presupuesto de 200ms termina deliberadamente esa comprobación antes. Los dos ID de la URL de registro repetida deben permanecer en el informe.
Guarda otro informe con dos workers. > reemplaza solo report.json; los errores siguen siendo visibles en el terminal.
go run . -input targets.json -workers 2 -timeout 200ms > report.json
Lee el archivo que has guardado realmente:
cat report.json
El orden de filas puede diferir de la entrada y entre ejecuciones. Relaciónalas por ID: una petición lenta puede terminar después de otra rápida asignada más tarde. Un worker no empieza su próximo destino hasta que regrese Check, pero un manejador cuyo cliente agotó el tiempo puede seguir realizando trabajo finito después de que este se haya ido. Por eso el curso mide el límite de workers en peticiones independientes que no agotan el tiempo; no confunde manejadores que siguen trabajando con un fallo del grupo.
El grupo también debe admitir menos tareas que workers o ninguna tarea. Estos comandos usan listas preparadas y muestran la salida directamente, dejando intacto report.json:
go run . -input single.json -workers 3 -timeout 2s
go run . -input empty.json -workers 3 -timeout 2s
Espera un resultado ok y después [], con ambos comandos terminando. Finalmente pide un límite de uno para varios destinos que no agotan el tiempo:
go run . -input overlap.json -workers 1 -timeout 2s
Todos los resultados siguen siendo ok. Esta invocación debe ser secuencial aunque el código pueda iniciar varios workers. El máximo acumulado conserva actividad anterior y no permite aislar el límite de esta invocación. La comprobación del curso crea un servicio nuevo e independiente para cada presupuesto, incluidos uno, dos y tres. Para lotes con varios destinos y presupuesto dos o tres, exige un solapamiento real de al menos dos sin superar el máximo solicitado; no exige un máximo exactamente igual a tres. Los lotes vacíos y de un solo destino no necesitan solapamiento.
Conserva el informe completo del evento, las entradas y el servicio originales. Ya tienes un comando reutilizable que combina trabajo activo limitado con todos los resultados, incluidos los fallos comunicados fielmente.
Resumen
Has usado canales de tareas y resultados con responsabilidades de cierre explícitas, un número fijo de workers y sincronización de su finalización. Un único recolector conserva todos los resultados sin carreras al modificar un slice compartido. Los eventos reales del servidor distinguen trabajo secuencial, concurrencia limitada y trabajo excesivo; las pruebas de tiempo máximo separadas evitan confundir manejadores aún activos con una infracción del límite. El informe guardado sigue siendo útil aunque fallen algunas entradas.



