[Go] 고루틴 Goroutine
2024. 11. 23. 16:58

Go 언어의 고루틴 (Goroutine)

- 고루틴은 Go 언어에서 제공하는 경량 스레드로, 병렬 처리를 쉽게 구현할 수 있는 기능

- 고루틴은 운영 체제의 스레드보다 훨씬 가볍고, 수천 개의 고루틴을 동시에 실행할 수 있음

- 채널(channel)과 함께 사용하여 고루틴 간의 통신과 동기화를 처리할 수 있음


고루틴의 특징

1. 경량성

- 고루틴은 운영 체제의 스레드와는 다르게 더 적은 리소스를 사용

- 하나의 Go 프로그램에서 수천 개 이상의 고루틴을 생성할 수 있음

2. 자동 관리

- Go 런타임이 고루틴의 실행과 스케줄링을 관리하므로 개발자가 직접 스레드 관리를 하지 않아도 됨

3. 간단한 구문

- go 키워드만 사용하면 함수를 고루틴으로 실행할 수 있음

4. 스택 크기

- 고루틴의 초기 스택 크기는 매우 작으며(약 2kb), 필요에 따라 자동으로 확장


고루틴 사용법

고루틴 기본 문법

go functionName(arguments)

- 함수 호출 앞에 go 키워드를 붙이면 해당 함수는 별도의 고루틴으로 실행


고루틴 예제

1. 간단한 고루틴

package main

import (
	"fmt"
	"time"
)

func printMessage(message string) {
	for i := 0; i < 5; i++ {
		fmt.Println(message)
		time.Sleep(500 * time.Millisecond) // 0.5초 대기
	}
}

func main() {
	// 고루틴 실행
	go printMessage("Hello from Goroutine")

	// 메인 함수에서도 동작
	for i := 0; i < 5; i++ {
		fmt.Println("Hello from Main")
		time.Sleep(500 * time.Millisecond) // 0.5초 대기
	}

	// 고루틴이 끝나기 전에 메인 함수 종료 방지
	time.Sleep(3 * time.Second)
}

출력 예시
Hello from Main
Hello from Goroutine
Hello from Main
Hello from Goroutine
...

2. 고루틴과 채널을 사용한 통신

- 고루틴은 보통 채널(channel)을 사용하여 데이터를 주고 받음

package main

import (
	"fmt"
)

func sum(a int, b int, result chan int) {
	result <- a + b // 채널에 결과 전달
}

func main() {
	// 채널 생성
	result := make(chan int)

	// 고루틴 실행
	go sum(3, 5, result)

	// 채널로부터 결과 수신
	total := <-result
	fmt.Println("Sum:", total)
}

출력 결과
Sum: 8

3. 여러 고루틴 실행

- 여러 고루틴을 실행하고 채널로 결과를 수집하는 예제

package main

import (
	"fmt"
	"time"
)

func worker(id int, result chan string) {
	fmt.Printf("Worker %d started\n", id)
	time.Sleep(1 * time.Second) // 작업 시뮬레이션
	result <- fmt.Sprintf("Worker %d done", id)
}

func main() {
	result := make(chan string, 3) // 버퍼 크기 3

	for i := 1; i <= 3; i++ {
		go worker(i, result)
	}

	for i := 1; i <= 3; i++ {
		fmt.Println(<-result)
	}
}

출력 결과
Worker 1 started
Worker 2 started
Worker 3 started
Worker 1 done
Worker 2 done
Worker 3 done

4. 고루틴 동기화

- 고루틴 간 동기화는 채널이나 sync패키지를 사용

1. WaitGroup을 활용한 동기화

sync.WaitGroup을 사용하면 고루틴의 종료를 기다릴 수 있음

package main

import (
	"fmt"
	"sync"
	"time"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done() // 고루틴 작업 완료 알림
	fmt.Printf("Worker %d started\n", id)
	time.Sleep(1 * time.Second)
	fmt.Printf("Worker %d done\n", id)
}

func main() {
	var wg sync.WaitGroup

	for i := 1; i <= 3; i++ {
		wg.Add(1) // 고루틴 추가
		go worker(i, &wg)
	}

	wg.Wait() // 모든 고루틴 완료 대기
	fmt.Println("All workers done")
}

출력 결과
Worker 1 started
Worker 2 started
Worker 3 started
Worker 1 done
Worker 2 done
Worker 3 done
All workers done

5. 다른 언어와의 차이점

1. 운영 체제 스레드와의 분리

- Go의 고루틴은 운영체제의 스레드와 직접 매핑되지 않음. Go 런타임이 스케줄러를 통해 효율적으로 스레드 풀에서 고루틴을 관리

- Java, Python 등에서는 스레드가 운영 체제 수준에서 직접 관리

2. 경량성

- Java나 C++에서 스레드는 상대적으로 많은 리소스를 소비

- 고루틴은 메모리와 CPU 사용량이 훨씬 적음

3. 간단한 문법

- Go에서는 go키워드만으로 고루틴을 실행할 수 있음

- 다른 언어에서는 스레드 생성과 실행에 복잡한 API 호출이 필요함


6. 고루틴의 한계와 주의점

1. 공유 메모리 문제

- 고루틴은 같은 메모리를 공유하므로 동기화 문제가 발생할 수 있음. sync.Mutex를 사용해 동기화를 처리

2. 잘못된 종료

- 고루틴이 끝나기 전에 메인 함수가 종료되면, 고루틴의 실행이 중단될 수 있음. 이를 방지하려면 채널이나 sync.WaitGroup을 사용

3. 과도한 생성

- 고루틴은 가볍지만, 너무 많이 생성하면 런타임 스케줄링에 부하가 걸릴 수 있음.


결론

고루틴은 Go 언어의 핵심 기능으로, 병렬 처리와 동시성 프로그래밍을 간단하고 효율적으로 구현할 수 있다.

실무에서는 고루틴과 채널을 함께 사용해 안정적이고 동기화된 프로그램을 개발하는 것이 중요함.

'프로그래밍 > Go' 카테고리의 다른 글

[Go] 채널 Channel 심화  (1) 2024.11.23
[Go] 채널 Channel 기초  (0) 2024.11.23
[Go] 인터페이스 interface  (0) 2024.11.23
[Go] 구조체 (Struct)  (0) 2024.11.22
[Go] Closure  (0) 2024.11.21