소개
Garden Night의 진입점 점검은 응답을 올바르게 분류하지만, 초기 구현은 작업을 하나씩 처리합니다. 느린 진입점 하나 때문에 다른 유용한 결과도 늦어집니다. 작고 고정된 수의 Go 워커가 일을 나눠 처리하면서도 모든 할당 작업의 결과를 반환하게 만듭니다.
이 독립 단원에는 한 대상을 점검하는 완성된 함수가 있습니다. 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의 준비된 작업을 수행합니다. 대상별 제한 시간을 2초로 설정해, 이번 관찰이 클라이언트 시간 초과가 아니라 동시성에 관한 것이 되도록 합니다. 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만 교체하며, 행사 서비스, 대상 정의, 점검 함수는 그대로 사용할 수 있습니다.
고정된 수의 워커에 작업 전달하기
이 단계에서는 작업과 결과의 흐름을 설계한 뒤 Workspace에서 Run을 편집합니다. 완성된 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 같은 선언은 0으로 시작합니다. 각 워커를 시작하기 전에 finished.Add(1)을 호출하고 워커 안에서 defer finished.Done()으로 종료를 알립니다. finished.Wait()는 등록된 모든 워커가 Done을 호출할 때까지 기다립니다. 그 기다림이 끝난 후에만 결과 채널을 닫을 수 있습니다. 기다리고 닫는 책임은 별도 고루틴에 맡기세요. 메인 고루틴이 수신 전에 기다리면 워커가 결과 전송에서 막힌 채 남을 수 있습니다. 생산자도 별도로 실행해야 작업을 할당하는 동안 메인 고루틴이 결과를 받을 수 있습니다.
요청한 수의 워커를 시작하는 for i := 0; i < workers; i++ { ... } 같은 계수 반복문은 i를 0으로 초기화하고 매 반복 전에 상한을 검사하며 반복 후 i를 늘립니다. 본문은 매번 워커 하나를 시작합니다. 각 워커는 Check를 동기적으로 호출하고 반환된 결과를 보낸 뒤에 다음 작업을 받아야 합니다. 따라서 활성 클라이언트 점검은 워커 수로 제한됩니다. 워커 안에서 대상마다 새 고루틴을 시작하지 마세요. Timeout: budget인 http.Client 하나를 공유합니다. 이 클라이언트는 동시 요청을 지원합니다. 수집자만 보고서 슬라이스에 추가하므로 워커가 공유 슬라이스나 map을 두고 경쟁하지 않습니다. 완료 순서는 달라도 됩니다.
대상 목록이 비어 있어도 생산자는 jobs를 닫습니다. 그러면 워커가 종료하고 종료 담당이 results를 닫으며 수집자는 초기화한 빈 슬라이스를 반환할 수 있습니다. 코드를 쓰기 전에 이 순서를 추적하세요. 워커 세 개와 대상 하나가 왜 종료해야 하는지, 워커 하나와 여러 대상이 왜 순차적이어야 하는지도 설명합니다.
하나로 연결된 작업 반복문 편집하기
두 번째 Run 정의가 Go에 컴파일되지 않도록 .go가 아닌 확장자로 백업합니다. 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으로 답하세요. 원하지 않는 파일명 입력창은 Ctrl+C로 취소하여 편집을 고치거나 원래 파일명으로 저장할 수 있습니다.
함수 시그니처와 기존 HTTP 클라이언트는 유지하세요. WaitGroup을 사용할 때 import 그룹에 sync를 추가합니다. 다음 책임을 순서대로 구현하며 순차 대상 반복문을 자신의 설계로 교체하세요.
먼저 타입이 지정된 작업/결과 채널과 워커 완료용 WaitGroup을 만듭니다. 정확히 workers개의 워커를 등록하고 시작하세요. 각 워커는 jobs가 닫힐 때까지 수신하고 Check의 모든 반환 결과를 보내며 반환할 때 완료를 알립니다.
이어서 서로 다른 ID로 반복된 URL도 포함하여 모든 대상을 보내는 별도 생산자를 시작합니다. 대상이 없을 때도 포함해 마지막 전송 후 jobs를 닫으세요.
또한 모든 워커를 기다린 뒤 results를 닫는 별도 완료 고루틴을 시작합니다. 워커가 아직 보낼 수 있는 동안 results를 닫으면 안 됩니다.
마지막으로 메인 고루틴에서 빈 결과 슬라이스를 초기화하고 결과 채널이 닫힐 때까지 모든 결과를 받아 하나씩 추가한 뒤 슬라이스를 반환합니다.
프로그램을 실행하기 전에 네 부분을 모두 작성하세요. 송신자, 종료 담당, 수집자가 없는 미완성 풀은 유용한 중간 실험이 아닙니다. 부분들이 연결되는 방식을 직접 결정하는 것이며 붙여 넣을 완성된 Run은 제공되지 않습니다. 지역 변수 이름이나 동등한 동기화 선택은 평가하지 않습니다.
저장하고 종료한 뒤 실제 파일을 확인합니다. cat은 변경하지 않고 표시합니다.
cat run.go
gofmt -w는 소스를 제자리에서 정리합니다. 문법 오류는 파일과 줄을 알려 줍니다. run.go를 다시 열고 해당 영역을 고친 뒤 저장하여 정리를 반복하세요. 새 전체 파일 답안으로 완성한 부분을 덮어쓰지 마세요.
gofmt -w run.go
실제로 겹치는 작업 확인하기
이제 완전한 풀을 시간 초과가 없는 작업에 워커 두 개로 실행합니다. 2초 예산이면 유한한 250ms 작업이 정상 종료할 수 있습니다.
go run . -input overlap.json -workers 2 -timeout 2s
각 작업은 ok 결과를 반환해야 합니다. 관측기의 기존 기록을 읽으세요.
python3 observe.py
이 환경에서는 누적 기록에 Actual handler peak: 2가 나타나고 다른 핸들러가 활성인 동안 enter가 있어야 합니다. 최고치가 계속 1이면 워커가 어떻게 수신하고 Check를 호출하는지 확인하세요. 경과 시간은 성공을 증명하지 못합니다. 비정상적인 스케줄러 지연은 관찰을 불확실하게 만들 수 있습니다. 최신 기록이 exit와 active=0으로 끝나면 유한한 요청이 실제 종료한 것입니다. 안전한 배치를 다시 실행하여 새 이벤트를 읽어도 됩니다.
누적 최고치가 2를 넘으면 어느 시점에 상한 초과가 있었다는 뜻입니다. 이전의 잘못된 구현은 소스를 고친 뒤에도 최고치를 남깁니다. 최신 진입/종료 이벤트를 함께 읽고 독립 테스트 환경에서 원래 소스를 실행하는 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 두 건, 상태 403인 http_error, 200인 content_mismatch, 0인 timeout, 0인 connection_error입니다. 티켓 서비스의 지연은 유한한 600ms이며, 200ms 클라이언트 예산이 의도적으로 점검을 먼저 끝냅니다. 반복되는 접수 URL의 두 ID를 모두 보고서에 남겨야 합니다.
워커 두 개로 새 보고서를 저장합니다. >는 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 한 건, 그다음 []가 나오고 둘 다 종료해야 합니다. 마지막으로 시간 초과가 없는 여러 대상에 상한 1을 요청합니다.
go run . -input overlap.json -workers 1 -timeout 2s
결과는 모두 ok입니다. 코드가 여러 워커를 시작할 수 있어도 이 호출은 순차적으로 처리해야 합니다. 관측기의 누적 최고치는 이전 활동을 보존하므로 이 호출의 상한만 따로 판단할 수 없습니다. 코스 검증은 1, 2, 3을 포함한 각 워커 예산에 대해 독립된 새 서비스를 만듭니다. 예산이 2나 3인 여러 대상에는 실제 겹침이 최소 두 건 있어야 하며 지정 상한을 넘으면 안 됩니다. 최고치가 정확히 3일 필요는 없습니다. 빈 집합과 단일 대상에는 겹침 조건이 없습니다.
전체 행사 보고서, 원본 입력, 서비스를 유지하세요. 이제 활성 작업 수를 제한하면서 솔직한 실패를 포함한 모든 결과를 제공하는 재사용 가능한 명령이 완성되었습니다.
요약
닫기 책임이 명확한 작업·결과 채널, 고정 워커 수, 종료 동기화를 사용했습니다. 단일 수집자가 공유 슬라이스의 수정 경쟁 없이 모든 결과를 유지합니다. 실제 서버 이벤트로 순차 처리, 상한이 있는 동시 처리, 과도한 작업을 구분했습니다. 독립된 시간 초과 테스트는 남아 있는 서버 처리를 워커 상한 위반으로 오해하지 않게 합니다. 일부 진입점이 실패해도 저장한 보고서는 유용합니다.



