ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Go 언어의 Goroutine이란?
    Dev 2025. 3. 2. 19:02
    728x90

    GoroutineGo에서 병렬(동시성) 실행을 지원하는 경량 스레드(Lightweight Thread)입니다.

    주요 특징:

    • Go 런타임이 관리하는 User-Level Thread (사용자 수준 스레드)
    • OS 스레드보다 가벼움 (수천 개의 Goroutine 생성 가능)
    • 자동 스케줄링 지원 (M:N 스케줄링)
    • go 키워드를 사용하여 생성됨
    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func sayHello() {
    	fmt.Println("Hello, Goroutine!")
    }
    
    func main() {
    	go sayHello() // Goroutine 생성
    	time.Sleep(time.Second) // 메인 함수가 종료되지 않도록 대기
    }

     

    📌 Goroutine은 User-Level Thread인가?

    1. User-Level Thread (ULF)와 Goroutine 비교

    특징OS ThreadUser-Level ThreadGoroutine

    관리 주체 OS 커널 사용자 공간 (라이브러리) Go 런타임
    컨텍스트 스위칭 비용 높음 낮음 매우 낮음
    생성 비용 높음 (OS 리소스 필요) 낮음 매우 낮음
    스케줄링 방식 OS 커널이 직접 관리 라이브러리가 직접 관리 Go 런타임의 M:N 스케줄링
    수천 개 실행 가능? ❌ (OS 제한 있음)

    ✅ Goroutine은 User-Level Thread처럼 동작하지만, OS 스레드에 직접 매핑되지 않음
    ✅ Go 런타임이 OS 스레드와 Goroutine 간 M:N 스케줄링을 통해 관리
    OS 스레드보다 가볍고, 자동으로 조정됨 (Preemptive Scheduling)

     

     

    📌 go 키워드를 사용한 Goroutine 생성

    Go에서는 go 키워드를 사용하여 Goroutine을 생성할 수 있음.

    🚀 기본적인 Goroutine 예제

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func printNumbers() {
    	for i := 1; i <= 5; i++ {
    		fmt.Println(i)
    		time.Sleep(time.Millisecond * 500) // 0.5초 대기
    	}
    }
    
    func main() {
    	go printNumbers() // Goroutine 실행
    
    	fmt.Println("Main function is running")
    	time.Sleep(time.Second * 3) // 메인 함수가 종료되지 않도록 대기
    	fmt.Println("Main function exits")
    }

     

    🛠 실행 흐름

    1. printNumbers()가 Goroutine으로 실행됨.
    2. 메인 함수는 "Main function is running"을 출력하고 종료되지 않도록 time.Sleep()을 호출.
    3. Goroutine은 백그라운드에서 실행되다가 메인 함수가 종료되면 같이 종료됨.

    중요: go printNumbers()를 호출했지만, 메인 함수가 먼저 종료되면 Goroutine도 종료됨.

     

    📌 Goroutine의 M:N 스케줄링 (Go 런타임)

    🚀 Goroutine과 OS 스레드의 관계

    Go는 M:N 스케줄링 모델을 사용하여 많은 수의 Goroutine을 적은 수의 OS 스레드에 매핑함.

    핵심 개념

    • M개의 GoroutineN개의 OS 스레드 위에서 실행됨.
    • Go 런타임(Goroutine Scheduler)이 자동으로 OS 스레드를 적절히 할당함.
    • 시스템 호출(syscall) 또는 블로킹 연산이 발생하면, 다른 OS 스레드에서 실행되도록 자동 조정됨.
     
    [Goroutine 1] ────> [OS Thread 1]
    [Goroutine 2] ────> [OS Thread 2]
    [Goroutine 3] ────> [OS Thread 1] (스위칭)
    [Goroutine 4] ────> [OS Thread 2]

    Go 런타임이 Goroutine을 효율적으로 OS 스레드에 배치하여 실행 성능을 최적화함.

     

    📌 Goroutine이 멈추지 않게 하려면?

    메인 함수가 종료되면 Goroutine도 함께 종료됨. 이를 방지하려면 sync.WaitGroup 또는 **채널(channel)**을 사용해야 함.

    1. sync.WaitGroup을 사용한 Goroutine 관리

    package main
    
    import (
    	"fmt"
    	"sync"
    )
    
    func printMessage(wg *sync.WaitGroup) {
    	defer wg.Done() // Goroutine 종료 시 `Done()` 호출
    	fmt.Println("Hello from Goroutine!")
    }
    
    func main() {
    	var wg sync.WaitGroup
    	wg.Add(1) // Goroutine 개수 추가
    
    	go printMessage(&wg) // Goroutine 실행
    
    	wg.Wait() // 모든 Goroutine이 끝날 때까지 대기
    	fmt.Println("Main function exits")
    }

     

    설명

    • wg.Add(1) → Goroutine 추가.
    • wg.Done() → Goroutine이 끝나면 Done() 호출.
    • wg.Wait() → 모든 Goroutine이 끝날 때까지 대기.

     

    2. 채널을 사용한 Goroutine 동기화

    package main
    
    import (
    	"fmt"
    )
    
    func worker(done chan bool) {
    	fmt.Println("Worker started")
    	done <- true // 채널에 값 전송 (완료 알림)
    }
    
    func main() {
    	done := make(chan bool)
    
    	go worker(done) // Goroutine 실행
    
    	<-done // 채널에서 값이 올 때까지 대기
    	fmt.Println("Worker finished")
    }

     

    설명

    • done 채널을 통해 Goroutine이 끝났음을 알림.
    • <-done으로 대기하면서 Goroutine이 종료될 때까지 기다림.

    📌 Goroutine vs OS 스레드 성능 비교

    package main
    
    import (
    	"fmt"
    	"runtime"
    	"time"
    )
    
    func main() {
    	fmt.Println("CPU Cores:", runtime.NumCPU())      // 사용 가능한 CPU 코어 수
    	fmt.Println("Goroutines before:", runtime.NumGoroutine()) // 현재 실행 중인 Goroutine 개수
    
    	for i := 0; i < 1000; i++ {
    		go func() {
    			time.Sleep(time.Second)
    		}()
    	}
    
    	fmt.Println("Goroutines after:", runtime.NumGoroutine()) // 1000개 이상의 Goroutine 실행
    	time.Sleep(time.Second * 2) // Goroutine이 실행될 시간을 줌
    }

     

    실행 결과

    CPU Cores: 8
    Goroutines before: 1
    Goroutines after: 1001
     
    • Goroutine은 수천 개 이상 실행 가능OS 스레드보다 가벼움!
    • OS 스레드는 보통 몇 백 개 이상 실행하면 성능이 크게 저하됨.

     

    🚀 결론

    ✅ Goroutine은 Go 런타임이 관리하는 User-Level Thread

    • OS 스레드보다 가벼움.
    • go 키워드를 사용하여 생성.

    M:N 스케줄링을 사용하여 최적화

    • 여러 개의 Goroutine이 적은 수의 OS 스레드에서 실행됨.
    • Go 런타임이 자동으로 최적화.

    Goroutine이 멈추지 않게 하려면?

    • sync.WaitGroup 또는 채널(channel) 사용.

    OS 스레드 대비 장점

    • 수천 개의 Goroutine 실행 가능.
    • 컨텍스트 스위칭 비용이 낮음.

    📌 즉, Goroutine은 OS 스레드보다 가볍고 Go의 동시성(Concurrency)을 강력하게 지원하는 핵심 기능! 🚀

    728x90
Designed by Tistory.