[Golang] Goroutine & Channel

Minsoo, Kim·2022년 11월 30일
1

Golang

목록 보기
1/2

Goroutine(고루틴)

고루틴은 Go 런타임이 관리하는 가상쓰레드 입니다.
Go에서 go 키워드를 사용하여 함수를 호출하면, Go는 런타임시에 새로운 고루틴을 생성합니다. 고루틴은 비동기적으로 함수를 실행하므로, 여러 함수를 동시에 실행하고자 할 때 사용됩니다.
고루틴은 OS의 쓰레드보다 더 가볍게 비동기 동시처리를 구현하기 위해 만들어 졌습니다. 때문에 고루틴은 OS의 쓰레드와 1:1로 대응되지 않고 훨씬 적은 OS의 쓰레드를 사용합니다.
Go 런타임이 실행하는 모든 프로그램은 고루틴을 기반으로 동작합니다. main 함수도 고루틴으로 실행됩니다. 때문에 모든 Go 프로그램은 적어도 하나의 (메인)고루틴을 포함합니다.

Goroutine VS Thread

package main

func hello(name string) {
	fmt.Printf("%s says hello.", name)
}

func main() {
	go hello("Alice")
    go hello("Bob")
    go hello("James")
    go hello("Sophia")
}

WaitGroup

Go 프로그램은 main함수 (main고루틴)가 종료되면 모든 (sub)고루틴들도 강제로 종료됩니다. 때문에 메인고루틴은 모든 서브고루틴이 다 실행됐을 때까지 기다려줘야 하는데 이때 WaitGroup을 사용합니다.
WaitGroup은 서브고루틴이 종료될 때까지 메인고루틴이 먼저 종료되지 않도록 해줍니다.

package main

var wg sync.WaitGroup

func hello(name string) {
	defer wg.Done()
	fmt.Printf("%s says hello.", name)
}
func bye(name string) {
	defer wg.Done()
	fmt.Printf("%s says hello.", name)
}

func main() {
    wg.add(2)
	go hello("Alice")
    go bye("Bob")
    wg.Wait()
}

WaitGroup을 사용하기 위해서 먼저 Add() 함수로 대기해야 하는 고루틴의 개수를 지정합니다. 그리고 각 고루틴에서 Done()을 호출하도록 합니다. main()에서는 Wait()를 호출하여 각 고루틴에서 Done()이 모두 호출될 때까지 기다리도록 합니다.

채널(Channel)

채널은 고루틴간에 데이터를 주고 받는 통로입니다.
채널은 make() 함수를 통해 미리 생성되어야 하며, <- 연산자를 이용하여 채널로 데이터를 보내거나 받을 수 있습니다.
채널은 비동기로 동작하는 고루틴을 동기로 제어하기 위한 목적으로 활용되기도 합니다. 채널에는 수신자와 송신자가 서로를 기다리는 속성이 있기 때문입니다.

package main

var wg sync.WaitGroup
func main() {
	wg.Add(4)
	ch := make(chan int)
	go func() {
		fmt.Print("I ")
		time.Sleep(1 * time.Second)
		ch <- 1
		wg.Done()
	}()
	<-ch
	go func() {
		fmt.Print("am ")
		time.Sleep(1 * time.Second)
		ch <- 2
		wg.Done()
	}()
	<-ch
	go func() {
		fmt.Print("Iron ")
		time.Sleep(1 * time.Second)
		ch <- 3
		wg.Done()
	}()
	<-ch
	go func() {
		fmt.Print("Man")
		time.Sleep(1 * time.Second)
		ch <- 4
		wg.Done()
	}()
	<-ch

	wg.Wait()

위의 코드에서는 4개의 익명함수로 된 서브고루틴이 다 실행되고 메인고루틴이 종료되도록 WaitGroup을 사용했습니다. 4개의 서브고루틴을 순차적으로 제어하기 위해, 채널에 데이터를 넣고 빼는 코드를 삽입했습니다. 코드를 실행하면 맨 처음 익명함수부터 메인고루틴과 데이터를 주고 받으며 순차적으로 실행됩니다. 익명함수에서 채널로 데이터를 보내면 다른 서브고루틴은 채널에서 데이터를 꺼낼때까지 블로킹 됩니다. 메인고루틴에서 채널로부터 데이터를 꺼내면 이후 다음 서브고루틴이 실행됩니다.

채널 버퍼링

Go 채널은 Unbuffered Channel과 Buffered Channel 두가지가 있습니다.

Unbuffered Channel

make()로 채널을 생성할 때, 버퍼 개수를 지정하지 않고 생성하면 Unbuffered Channel이 만들어 집니다.
Unbuffered Channel은 하나의 수신자가 데이터를 받을 때까지 송신자는 데이터를 보낸 채널에 블로킹 됩니다.

Buffered Channel

make()로 채널을 생성할 때, 버퍼 개수를 지정하고 생성하면 Buffered Channel이 만들어 집니다. Buffered Channel은 수신자가 데이터를 받을 때까지 송신자가 블로킹 되지 않고 다른 작업을 계속 수행할 수 있습니다. 채널의 버퍼가 가득차면 송신자는 블로킹 되며 수신자가 데이터를 꺼낼 때까지 대기합니다.

채널 close()

채널을 생성해서 데이터를 주고 받은 와중에 close()함수로 채널을 닫을 수 있습니다. 채널을 닫게 되면 해당 채널로 더이상 데이터를 보낼 수 없습니다. 하지만 계속 수신은 받을 수 있습니다. close() 함수는 채널로 루프를 돌리고자 할 때 활용될 수 있습니다.

채널 range문

for range문으로 채널을 사용하면 for 루프는 채널이 닫힐 때까지 루프를 실행하다가 채널이 닫히면 루프를 종료합니다.

	for i := range ch {
    	fmt.Println(i)
    }

2개의 댓글

comment-user-thumbnail
2022년 12월 26일

좋은글 감사합니다 :)

답글 달기
comment-user-thumbnail
2024년 3월 14일

WaitGroup 샘플 중에
wg.add(2)
대신에
wg.Add(2)
라고 대문자로 입력 하셔요~

답글 달기