본문 바로가기

Programming Language/golang

golang 동시성 예제

golang은 동시성 프로그램을 위해 태어난 언어라고 해도 과언이 아닙니다. 예제를 통해 golang이 동시성을 만족하는 코드를 작성하여 보여드리겠습니다. 동시성을 위해 goroutine과 channel 개념을 사용합니다. 각 개념은 아래 링크에서 확인할 수 있습니다.

- goroutine : golang.site/go/article/21-Go-%EB%A3%A8%ED%8B%B4-goroutine

Go루틴(goroutine)은 Go 런타임이 관리하는 Lightweight 논리적 (혹은 가상적) 쓰레드(주1)이다. Go에서 "go" 키워드를 사용하여 함수를 호출하면, 런타임시 새로운 goroutine을 실행한다. goroutine은 비동기적으로(asynchronously) 함수루틴을 실행하므로, 여러 코드를 동시에(Concurrently) 실행하는데 사용된다.

- channel : golang.site/go/article/22-Go-%EC%B1%84%EB%84%90

Go 채널은 그 채널을 통하여 데이타를 주고 받는 통로라 볼 수 있는데, 채널은 make() 함수를 통해 미리 생성되어야 하며, 채널 연산자 <- 을 통해 데이타를 보내고 받는다. 채널은 흔히 goroutine들 사이 데이타를 주고 받는데 사용되는데, 상대편이 준비될 때까지 채널에서 대기함으로써 별도의 lock을 걸지 않고 데이타를 동기화하는데 사용된다.

 

한 개의 메서드에서 여러개 웹페이지로부터 리소스를 요청하는 경우

동시성을 사용하지 않은 경우

package main

import (
	"fmt"
	"math/rand"
	"time"
)

var (
	Web   = fakeSearch("web")
	Image = fakeSearch("Image")
)

type Search func(query string) Result
type Result struct {
	Title, URL string
}

func main() {
	fmt.Println("sync")
	syncResult := CallWebpage("hi")
	fmt.Println(syncResult)
}

func CallWebpage(query string) (results []Result) {
	results = append(results, Web(query))
	results = append(results, Image(query))
	return results
}

func fakeSearch(kind string) Search {
	return func(query string) Result {
		time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
		fmt.Printf("%s result for %q \n", kind, query)
		return Result{kind, "url" + kind}
	}
}

여기서 CallWebpage는 고루틴과 채널을 사용하지 않은 일반적인 절차기반으로 작성하였다. fakeSearch()메서드를 호출 할 때 100ms이내의 시간이 걸리는 것을 가정하였다. Web()을 호출하고 나서 Image()를 호출하므로 최대 200ms를 기다리게 된다.

동시성을 사용한 경우

package main

import (
	"fmt"
	"math/rand"
	"time"
)

var (
	Web   = fakeSearch("web")
	Image = fakeSearch("Image")
)

type Search func(query string) Result
type Result struct {
	Title, URL string
}

func main() {
	fmt.Println("goResult")
	goResult := GoRoutineWebpage("hi")
	fmt.Println(goResult)
}
func GoRoutineWebpage(query string) (results []Result) {

	c := make(chan Result)

	go func() { c <- Web(query) }()
	go func() { c <- Image(query) }()

	for i := 0; i < 2; i++ {
		result := <-c
		results = append(results, result)
	}

	return results
}
func fakeSearch(kind string) Search {
	return func(query string) Result {
		time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
		fmt.Printf("%s result for %q \n", kind, query)
		return Result{kind, "url" + kind}
	}
}

여기서 특이한점은 for구문에서 채널의 리턴을 2번 기다린다는 점이다. 왜냐면 고루틴 함수가 2번 호출되기 때문에 for구문에서 2번 기다린다. 만약 3번을 기다리면 어떻게 될까? for구문에 있는 i변수를 3으로 변경하고 실행하면 아래와 같이 오류가 발생한다.

fatal error: all goroutines are asleep - deadlock!

현재 c라는 이름의 채널에 할당된 고루틴이 없음에도 불구하고 기다리는 것을 가용하였기 때문에 무한 sleep에 빠지게 된다. 그렇기 때문에 golang은 runtime에 daedlock 에러를 내면서 애플리케이션을 종료한다.

반응형