[GoLang] net/http에서 http 요청을 동시적으로 처리하는 이유

2025. 2. 17. 14:45· Go Lang/Study
목차
  1. 개요

개요

https://seung.tistory.com/entry/GoLang-Go%EC%97%90%EC%84%9C-%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EB%9E%80

 

[GoLang] Go에서 동시성이란

Go  스케줄링고루틴 : Go의 최소 실행 단위로, main도 profile도 모든 게 다 고루틴고의 동시성에서는 Go 스케줄러로 어떻게 효율적으로 관리하느냐에 크게 의존합니다. goroutine 생성새로운 작업(Gorou

seung.tistory.com

요즘 고루틴에 대한 기억을 다시 떠올리며 공부하고 있습니다.

생각해 보니 GoLang을 http 서버로 이용할 때도 고루틴으로 선언 안 해도 모든 요청을 고루틴으로 돌리더군요.

즉 샘플 코드를 안만들어도, 이렇게 가까이에 좋은 예제가 있던 겁니다.

 

다음처럼 아주 간단한 샘플 코드를 만들어봅시다.

package main

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

func helloHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "Hello, World!")
}

func main() {
	http.HandleFunc("/", helloHandler)

	log.Println("Starting server on localhost:8080")
	if err := http.ListenAndServe("localhost:8080", nil); err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

이 예제에서 localhost:8080으로 요청을 동시적으로 수만 건을 보내도 고는 동시적으로 처리할 겁니다.

 

ListenAndServe에서 이 부분을 처리하기 때문이죠.

이는 go의 기본 라이브러리인 http 패키지에서 제공하는 함수이고, 이번에 설명할 전체 내부 로직은 아래와 같습니다.

func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

func (s *Server) ListenAndServe() error {
	if s.shuttingDown() {
		return ErrServerClosed
	}
	addr := s.Addr
	if addr == "" {
		addr = ":http"
	}
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	return s.Serve(ln)
}

func (s *Server) Serve(l net.Listener) error {
	if fn := testHookServerServe; fn != nil {
		fn(s, l) // call hook with unwrapped listener
	}

	origListener := l
	l = &onceCloseListener{Listener: l}
	defer l.Close()

	if err := s.setupHTTP2_Serve(); err != nil {
		return err
	}

	if !s.trackListener(&l, true) {
		return ErrServerClosed
	}
	defer s.trackListener(&l, false)

	baseCtx := context.Background()
	if s.BaseContext != nil {
		baseCtx = s.BaseContext(origListener)
		if baseCtx == nil {
			panic("BaseContext returned a nil context")
		}
	}

	var tempDelay time.Duration // how long to sleep on accept failure

	ctx := context.WithValue(baseCtx, ServerContextKey, s)
	for {
		rw, err := l.Accept()
		if err != nil {
			if s.shuttingDown() {
				return ErrServerClosed
			}
			if ne, ok := err.(net.Error); ok && ne.Temporary() {
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}
				s.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			return err
		}
		connCtx := ctx
		if cc := s.ConnContext; cc != nil {
			connCtx = cc(connCtx, rw)
			if connCtx == nil {
				panic("ConnContext returned nil")
			}
		}
		tempDelay = 0
		c := s.newConn(rw)
		c.setState(c.rwc, StateNew, runHooks) // before Serve can return
		go c.serve(connCtx)
	}
}

 

이 코드는 우선 ListenAndServe로 TCP 리스너를 만들고, 이걸 s.Serve로 실행시킵니다.

TCP 리스너로 만듦으로 정해진 포트로 들어오는 TCP 연결을 받아 HTTP 요청을 처리하죠

func (s *Server) ListenAndServe() error {
	...
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	return s.Serve(ln)
}

 

Serve에서 아래의 순서대로 동작합니다.

  1. l.Accept() 호출을 통해 새로운 TCP 연결을 수신해서 무한루프에서 요청이 들어올 때까지 대기
  2. 에러 발생 시, 백오프(back-off) 전략을 사용하여 잠시 대기한 후 다시 연결을 수신
  3. 서버의 context에 정보를 넣어 이후 단계에서 활용할 수 있게 함
  4. 고루틴을 생성함으로써 HTTP 요청은 모두 독립적이고 병렬적으로 실행
func (s *Server) Serve(l net.Listener) error {
    ...
    ctx := context.WithValue(baseCtx, ServerContextKey, s)
    for {
       // TCP 요청 대기
       rw, err := l.Accept()
       if err != nil {
          if s.shuttingDown() {
             return ErrServerClosed
          }
          // 에러 발생시 재시도
          if ne, ok := err.(net.Error); ok && ne.Temporary() {
             if tempDelay == 0 {
                tempDelay = 5 * time.Millisecond
             } else {
                tempDelay *= 2
             }
             if max := 1 * time.Second; tempDelay > max {
                tempDelay = max
             }
             s.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
             time.Sleep(tempDelay)
             continue
          }
          // 일시적이지 않은 에러는 에러 반환
          return err
       }
       // 새로 수신한 연결에 대해 컨텍스트 확장
       connCtx := ctx
       if cc := s.ConnContext; cc != nil {
          connCtx = cc(connCtx, rw)
          if connCtx == nil {
             panic("ConnContext returned nil")
          }
       }
       // 에러가 없으니, 기다리지 않음
       tempDelay = 0
       c := s.newConn(rw)
       // 연결 상태를 새 연결로 설정
       c.setState(c.rwc, StateNew, runHooks) // before Serve can return
       // 수신된 연결마다 새로운 고루틴 생성하여 요청 처리
       go c.serve(connCtx)
    }
}

여태까지 잘 사용해 왔던 http 서버는 이런 식으로 돌아가던 겁니다.

 

회사 코드 중에 아래와 같이 돌아가던 코드가 있는데

이건 왜 이렇게 하신지 모르겠네요, 5년 전에 작성했고 그분은 이미 퇴사했습니다...

제가 모르는 분야가 있을 수 있으니 수정은 보류입니다

go func() {
	if err := server.ListenAndServe(); err != nil {
		// elog.Info(1, fmt.Sprintf("starting server error : %v", err))
		logger.Errorf("starting server error : %v\n", err)
	}
}()
반응형

'Go Lang > Study' 카테고리의 다른 글

[GoLang] REST API, gRPC 비교  (0) 2025.03.15
[GoLang] 시간 초과 로직 Context, 채널과 비교  (0) 2025.02.25
[GoLang] GC 튜닝 Profiling  (0) 2025.02.05
[GoLang] Garbage Collector 개념  (0) 2025.01.29
[GoLang] 추상 팩토리 디자인 사용해보기  (2) 2025.01.17
  1. 개요
'Go Lang/Study' 카테고리의 다른 글
  • [GoLang] REST API, gRPC 비교
  • [GoLang] 시간 초과 로직 Context, 채널과 비교
  • [GoLang] GC 튜닝 Profiling
  • [GoLang] Garbage Collector 개념
DSeung
DSeung
DSeung
Dev log
DSeung
  • 분류 전체보기 (193)
    • PHP (62)
      • Laravel (31)
      • Error (5)
      • Setting (11)
      • Modern PHP (15)
    • Go Lang (51)
      • Study (30)
      • Algorithm (17)
      • Setting (1)
      • Error (3)
    • Java (11)
      • Spring (3)
      • JSP (0)
      • Error (2)
      • Setting (2)
      • 단축키 (2)
    • JavaScript (6)
      • Modern JavaScript (4)
      • Node (1)
    • Android Kotlin (5)
      • Study (4)
      • Error (1)
    • 컴퓨팅 기술 (12)
      • 데이터베이스시스템 (4)
      • Docker (2)
      • 크롤링 & 스크래핑 (1)
      • API (1)
      • 클라우드 (1)
      • 네트워크 (1)
    • MySQL (7)
    • AWS (1)
    • Git (5)
      • GItLab (1)
      • GitHub (4)
    • 도메인 (2)
      • 안과 (2)
    • 자격증 (7)
      • SQLD (1)
      • 정보처리기사 (6)
    • Mac os (1)
    • 나머지 (13)
      • tistory (1)
      • 기타 (9)
      • 일기 (3)
    • 독서 (10)

인기 글

최근 글

블로그 메뉴

  • 홈
  • 태그
전체
오늘
어제
hELLO · Designed By 정상우.v4.2.0
DSeung
[GoLang] net/http에서 http 요청을 동시적으로 처리하는 이유
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.