본문으로 바로가기

언제 Go 언어가 괜찮을까?

category Language/Go 2026. 1. 20. 10:18
728x90
반응형
SMALL

Go 언어

Go 언어는 goroutine 기반 병행성 모델과 빠른 컴파일 속도를 바탕으로 I/O 중심 서버에 강점을 가진다. 
Spring Boot 환경에서 운영 중인 서비스 중 일부는 Go로 전환했을 때 성능과 비용 측면에서 더 큰 이점을 얻을 수 있다. 
필자는 Spring Boot 를 주로 사용하는데 그러면 만약 Go로 전환하면 가장 효과가 좋은 영역은 어디인가?

 

✅ 1. 트래픽 많고 단순한 API 서버

  • 조회(Read) 위주의 API
  • 단순 CRUD
  • 인증 토큰 검증
  • 외부 시스템 중계 API (Proxy)

왜 Go가 좋은가

  • JVM 힙/GC 부담 없음
  • 메모리 사용량 작음
  • 빠른 cold start → 컨테이너/서버리스에 유리
  • TPS 대비 비용 효율 좋음

👉 Spring Boot API Gateway / Edge API → Go
(특히 BFF, API Gateway 성격이면 매우 좋음)


✅ 2. 메시지 기반 Consumer / Worker

  • Kafka / RabbitMQ / IBM MQ Consumer
  • 이벤트 처리기
  • 비동기 작업 Worker
  • 로그 처리, 변환기

왜 Go가 좋은가

  • goroutine으로 동시성 처리 매우 쉬움
  • long-running process에 안정적
  • CPU/메모리 효율 뛰어남
  • 컨테이너로 여러 개 띄우기 쉬움

👉 Spring @KafkaListener / @JmsListener → Go Consumer


✅ 3. 배치성 작업 / 스케줄러

  • 정기 데이터 수집
  • 파일 변환
  • 외부 API polling
  • ETL 전처리

왜 Go가 좋은가

  • 단일 바이너리 배포
  • 크론 + Go 조합 단순
  • Spring Batch 대비 구조 단순

👉 Spring Batch → Go CLI / Worker



2️⃣ Go로 전환해도 되지만 신중해야 할 영역

⚠️ 4. 비즈니스 로직이 복잡한 핵심 도메인

  • 결제
  • 정산
  • 복잡한 트랜잭션
  • DDD 기반 도메인 로직

이유

  • Spring의 강점:
    • 트랜잭션 관리
    • AOP
    • Validation
    • JPA/Hibernate
  • Go는 직접 다 만들어야 함

👉 이 영역은 Spring 유지 + 주변부만 Go 전환 추천


⚠️ 5. 관리자 페이지 / 백오피스

  • Spring + JPA + Security 조합이 생산성 좋음
  • Go는 ORM, Admin UI 모두 직접 구성 필요

3️⃣ 거의 안 건드리는 게 좋은 영역

❌ 6. 복잡한 ORM 기반 대규모 CRUD

  • JPA Entity 많음
  • Lazy loading 의존
  • 복잡한 연관관계

👉 이걸 Go로 옮기면 생산성 급감


go 설치

1.Go 설치

https://go.dev/dl/


2.환경변수

go를 설치할때 기본적으로 c드라이브에 설치하면 환경변수 작업을 안해줘도 되지만 내가 원하는 위치에 설치하고 싶을 경우 환경변수 설정을 해줘야 한다.

필자는 d드라이브에 설치했기 때문에 GOROOT 변수에 대한 경로를 잡아줘야 한다.

참고)원하는 위치에 go 설치시 go 폴더를 먼저 만들어주고 그 안에 go를 설치해야 한다.


3.vs code에서 확인하기

main.go 파일을 만들어주고 터미널에서 go 버전을 확인하자.

정상확인이 되었으면 잘 세팅 되었다는것이다.

$ go version

4.go의 폴더구조

go 1.10 이전버전(GOPATH 방식)

go에서 프로젝트 파일을 만드려고 한다면 일단

src 하위에 만들어야 한다.

그리고 주로 도메인/유저이름 구조로 생성되도록 권장된다.

GOPATH
 └─ src
     └─ github.com
         └─ username
             └─ myapp
                 └─ main.go

1.17+(GO Modules 방식)

D:\projects\myapp
 ├─ go.mod
 ├─ main.go
 └─ internal/

✔ GOPATH 필요 없음
✔ src 필요 없음
✔ 디렉토리 자유

go 모듈으로 실행시 초기설정

go mod init learngo //루트 폴더 설정

go 문법

접근자

대문자로 시작하는 메서드는 public 메서드이다.

something.go

package something

import "fmt"

/*
*  private
*/
func sayBye() {
    fmt.Println("Bye")
}

/*
*  public
*/
func SayHello() {
    fmt.Println("Hello")
}

main.go

package main

import (
    "fmt"

    "github.com/learngo/something"
)

func main() {
    fmt.Println("Hello World!")
    something.SayHello()
}

변수와 상수

go는 타입언어이다.

상수

package main

import "fmt"

func main() {
    const name string = "jyj"
    //const name = "jyj"  이런식으로 타입 추론도 가능
    fmt.Println(name)
}

//결과
jyj

상수로 선언했기 때문에 변경할 수 없고 변경시 에러가 발생한다.

func main() {
    const name string = "jyj"
    name = "test" //컴파일 단계 에러 발생
    fmt.Println(name)
}

변수

방법1

func main() {
    var name string = "jyj"
    name = "test"
    fmt.Println(name)
}

//결과
test

방법2

아래와 같이 타입 추론을 이용하여 선언도 가능하다.

package main

import "fmt"

func main() {
    name := "jyj"
    name = "test"
    fmt.Println(name)
}

변수의 타입추론시 첫번째 값을 기준으로 추론되기 때문에

name 값을 string으로 선언했가 때문에 다음에 name에 bool 값인 false를 넣으면 에러가 난다.

package main

import "fmt"

func main() {
    name := "jyj"
    name = false //컴파일 단계 에러 발생
    fmt.Println(name)
}

다양한 타입들

https://go101.org/article/type-system-overview.html


파라미터와 반환값

파라미터와 반환값에는 모두 타입이 명시되어 있어야 한다.

package main

import "fmt"

func multiply(a int, b int) int {
    return a * b
}

func main() {
    fmt.Println(multiply(2, 2))
}

파라미터 a,b의 타입이 같다면 아래와 같이 작성 가능하다.

func multiply(a,b int) int {
    return a * b
}

멀티 value

go는 다른 언어와 달리 다양한 타입을 동시에 return 해줄 수 있다.

package main

import (
    "fmt"
    "strings"
)

func lenAndUpper(name string) (int, string) {
    return len(name), strings.ToUpper(name)
}

func main() {
    totalLength, upperName := lenAndUpper("yjy")
    fmt.Println(totalLength, upperName)
}

//결과 
3 YJY

리턴 개수가 여러개일때 1개의 값만 받고 싶다면, 언더바(_)를 이용하자

func main() {
    totalLength, _ := lenAndUpper("yjy")
    fmt.Println(totalLength)
}

//결과 
3

여러개의 파라미터 처리

파라미터에 ... 선언만 해주면 여러 파라미터를 받을 수 있다.

package main

import "fmt"

func repeatMe(words ...string) {
    fmt.Println(words)
}

func main() {
    repeatMe("yjy", "test", "hi", "hello")
}

//결과
[yjy test hi hello]

naked return

return 만 적어주고 대상을 선언 할 필요가 없다

일반적인 V1을 naked return을 적용해 V2로 만들었다.

import (
    "fmt"
    "strings"
)

func lenAndUpperV1(name string) (int, string) {
    return len(name), strings.ToUpper(name)
}

func lenAndUpperV2(name string) (lenght int, uppercase string) {
    lenght = len(name)
    uppercase = strings.ToUpper(name)
    return
}

func main() {
    totalLength, upperName := lenAndUpperV2("yjy")
    fmt.Println(totalLength, upperName)
}

//결과
3 YJY

defer

function이 끝났을때 다른 작업을 실행시키고 싶을때

lenAndUpperV2의 작업이 모두 끝나고 나서 defer fmt.Println("I'm done")이 실행된다.

package main

import (
    "fmt"
    "strings"
)

func lenAndUpperV2(name string) (lenght int, uppercase string) {
    defer fmt.Println("I'm done")
    lenght = len(name)
    uppercase = strings.ToUpper(name)
    return
}

func main() {
    totalLength, upperName := lenAndUpperV2("yjy")
    fmt.Println(totalLength, upperName)
}

//결과
I'm done
3 YJY

for, range, ...args

go의 기본 loop는 오로지 for만 존재한다.

for 사용

package main

import (
    "fmt"
)

func superAdd(numbers ...int) int {
    for i := 0; i < len(numbers); i++ {
        fmt.Println(i)
    }
    return 1
}

func main() {
    superAdd(1, 2, 3, 4, 5, 6)
}
//결과
0
1
2
3
4
5

range 키워드 사용

인덱스만 출력

package main

import (
    "fmt"
)

func superAdd(numbers ...int) int {
    for number := range numbers {
        fmt.Println(number)
    }
    return 1
}

func main() {
    superAdd(1, 2, 3, 4, 5, 6)
}

//결과
0
1
2
3
4
5

인덱스와 값 같이 출력

package main

import (
    "fmt"
)

func superAdd(numbers ...int) int {
    for index, number := range numbers {
        fmt.Println(index, number)
    }
    return 1
}

func main() {
    superAdd(1, 2, 3, 4, 5, 6)
}

//결과
0 1
1 2
2 3
3 4
4 5
5 6

값만 출력하고 싶을 경우

func superAdd(numbers ...int) int {
    for _, number := range numbers {
        fmt.Println(index, number)
    }
    return 1
}

합계를 구하자

package main

import "fmt"

func superAdd(numbers ...int) int {
    total := 0
    for _, number := range numbers {
        total += number
    }
    return total
}

func main() {
    result := superAdd(1, 2, 3, 4, 5, 6)
    fmt.Println(result)
}

//결과
21

if, else

package main

import "fmt"

func canIDrink(age int) bool {
    if age < 18 {
        return false
    } else {
        return true
    }
}

func main() {
    fmt.Println(canIDrink(16))
}

//결과
false

if에 변수 적용

package main

import "fmt"

func canIDrink(age int) bool {
    if koreanAge := age + 2; koreanAge < 18 {
        return false
    } else {
        return true
    }
}

func main() {
    fmt.Println(canIDrink(16))
}
//결과
true

Switch

package main

import "fmt"

func canIDrinkV1(age int) bool {
    switch age {
    case 10:
        return false
    case 18:
        return true
    }
    return false
}

func canIDrinkV2(age int) bool {
    switch {
    case age < 18:
        return false
    case age == 18:
        return true
    case age > 50:
        return false
    }
    return false
}

func canIDrinkV3(age int) bool {
    switch koreanAge := age + 2; koreanAge {
    case 10:
        return false
    case 18:
        return true
    }
    return false
}

func main() {
    fmt.Println(canIDrinkV1(18))
    fmt.Println(canIDrinkV2(18))
    fmt.Println(canIDrinkV3(18))
}

//결과
true
true
false



Pointer

&는 메모리 주소

func main() {
    a := 2
    b := &a
    a = 10

    fmt.Println(a, b)
    fmt.Println(&a, b)
}

//결과
10 0xc00000a098
0xc00000a098 0xc00000a098

*은 메모리 주소가 가르키는 실제값을 가르킨다.

package main

import "fmt"

func main() {
    a := 2
    b := &a
    a = 5

    fmt.Println(*b)
}

//결과
5
func change(x *int) {
    *x = 20
}

a := 10
change(&a)
fmt.Println(a) // 20
package main

import "fmt"

func main() {
    a := 2
    b := &a
    *b = 20

    fmt.Println(a) //20
}



Arrays and Slices

Arrays

package main

import "fmt"

func main() {
    //사이즈 선언
    namesV1 := [3]string{"yjy", "hi", "hello"}
    namesV1[3] = "lala" //에러

    //사이즈 미선언시 slice 라고도 부른다
    namesV2 := []string{"yjy", "hi", "hello"}
    namesV2[3] = "lala"

    fmt.Println(namesV1)
    fmt.Println(namesV2)
}

Slices

package main

import "fmt"

func main() {
    names := []string{"yjy", "hi", "hello"}
    fmt.Println(names)

    names = append(names, "test")
    fmt.Println(names)
}

//결과
[yjy hi hello]
[yjy hi hello test]

Maps

map[키의타입 선언]value의타입선언

package main

import "fmt"

func main() {
    yjy := map[string]string{"name": "yjy", "age": "30"}
    fmt.Println(yjy)
}

추가

요소확인 여부

struct

package main

import "fmt"

type person struct {
    name    string
    age     int
    favFood []string
}

func main() {
    favFood := []string{"chicken, pizza"}
    // 가독성 좋음
    yjy := person{name: "yjy", age: 30, favFood: favFood}
    // 가독성 않좋음
    // yjy := person{"yjy", 30, favFood}
    fmt.Println(yjy)
}

struct에서도 소문자면 private 대문자면 public 이다

struct의 속성도 마찬가지이다.

//private
type person struct {
    name    string
    age     int
    favFood []string
}

//public
type Person struct {
    Name    string
    Age     int
    FavFood []string
}

function을 이용한 생성자 개념만들기

go에는 생성자가 따로 없다. 그래서 만들어 줘야 한다.

accounts.go

package accounts

// Account struct
type Account struct {
    owner   string
    balance int
}

// NewAccount creates Account
// &account을 리턴함으로서 새로 만들어진 account의 주소값을 반환한다.
// 늘 복사하면 프로그램 속도는 느려진다.
func NewAccount(owner string) *Account {
    account := Account{owner: owner, balance: 0}
    return &account
}

/*
    ✔️ 값 타입으로 리턴하면
    func NewAccount(owner string) Account {
        return Account{owner: owner, balance: 0}
    }


    호출 시점에 Account 전체가 복사
    필드가 많아질수록 복사 비용 증가
    이후 수정 시 또 복사 발생 가능

    ✔️ 포인터로 리턴하면
    주소값(8바이트)만 복사
    구조체 크기와 무관
    동일 객체를 여러 곳에서 공유 가능
    👉 그래서 구조체가 클수록 포인터 반환이 유리
*/

main.go

package main

import (
    "fmt"

    "github.com/learngo/accounts"
)

func main() {
    account := accounts.NewAccount("yjy")
    fmt.Println(account)
}

//결과
&{yjy 0}

receiver

Deposit 이라는 함수는 a라는 receiver를 갖고있다.

receiver는 struct의 첫 글자를 따서 소문자로 지어야 한다.

struct 가 Account 라면은 a 로 지어야 한다.

메서드 개념이라고 생각하면 편하다.

accounts.go

Deposit 이라는 메서드를 만들었다.

func NewAccount(owner string) *Account {
    account := Account{owner: owner, balance: 0}
    return &account
}

func (a Account) Deposit(amount int) {
    a.balance += amount
}

main.go

package main

import (
    "fmt"

    "github.com/learngo/accounts"
)

func main() {
    account := accounts.NewAccount("yjy")
    account.Deposit(10)

    fmt.Println(account.Balance())
}

//결과
0

결과가 0이 나왔다 왜그럴까?

NewAccount를 통해서 account 값을 먼저 만들었다. 그리고 Deposit을 통해 balance를 증가시키려고 한다. 아래 Deposit의 (a Account)에서 go는 복사값을 만든다. 그래서 원본값이 변하지 않는것이다.

func NewAccount(owner string) *Account {
    account := Account{owner: owner, balance: 0}
    return &account
}

func (a Account) Deposit(amount int) {
    a.balance += amount
}

때로는 메서드안에서 안전하게 복사값을 만들어서 핸들링하고 싶다면 이처럼 써도 되지만 원본값이 변경되야 하는 경우는 변경되야한다.

변경후

func (a *Account) Deposit(amount int) {
    a.balance += amount
}

main.go

...
func main() {
    account := accounts.NewAccount("yjy")
    account.Deposit(10)

    fmt.Println(account.Balance())
}

//결과
10

결과가 10으로써 의도대로 동작한다!.



error 핸들링

go는 exception이 따로 없기 때문에 error를 직접 핸들링 해줘야 한다.

accounts.go

...
// Withdraw x amount from your account
func (a *Account) Withdraw(amount int) error {
    if a.balance < amount {
        return errors.New("돈이 부족합니다")
    }
    a.balance -= amount
    return nil
}

main.go

package main

import (
    "fmt"
    "github.com/learngo/accounts"
)

func main() {
    account := accounts.NewAccount("yjy")
    account.Deposit(10)

    fmt.Println(account.Balance())

    err := account.Withdraw(20)
    //프로그램 종료
    //log.Fatalln(err)

    //주로 error 핸들링 방법
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(account.Balance())
}

//결과
10
돈이 부족합니다
10

다양한 에러들이 존재한다면?

error 들을 변수로 선언하고 사용하자

...
var errNoMoney = errors.New("돈이 없습니다.")

// Withdraw x amount from your account
func (a *Account) Withdraw(amount int) error {
    if a.balance < amount {
        return errNoMoney
    }
    a.balance -= amount
    return nil
}

참고사항) nil에 대해

nil이 될 수 있는 타입들

Go에서 nil“아무것도 가리키지 않음(없음)” 을 의미하는 미리 정의된 식별자(predeclared identifier) 입니다.

✅ nil 가능

  • 포인터 (*T)
  • 슬라이스 ([]T)
  • 맵 (map[K]V)
  • 채널 (chan T)
  • 함수 (func)
  • 인터페이스 (interface)

❌ nil 불가

  • 기본 타입: int, string, bool, struct, array


String 재정의

obj 호출시 go는 내부적으로 String 함수를 호출하게 되는데 해당 함수를 재정의할 수 있다.

accounts.go

...
func (a Account) String() string {
    return fmt.Sprint(a.Owner(), "'s account. \nHas: ", a.Balance())
}

main.go

func main() {
    account := accounts.NewAccount("yjy")

    fmt.Println(account)
}

//결과
yjy's account. 
Has: 10



Dictionary

struct 말고도 Dictionary를 통해 타입을 만들 수 있다.

mydict.go

package mydict

// Dictionary  type
type Dictionary map[string]string

main.go

package main

import (
    "fmt"

    "github.com/learngo/mydict"
)

func main() {
    dictionary := mydict.Dictionary{"first": "First word"}
    fmt.Println(dictionary)
}

//결과
map[first:First word]

Dictionary만의 메서드 만들기

Search라는 메서드를 만들었다.

mydict.go

package mydict

import "errors"

// Dictionary  type
type Dictionary map[string]string

var errNotFound = errors.New("Not Found")

// Search for a word
func (d Dictionary) Search(word string) (string, error) {
    value, exists := d[word]
    if exists {
        return value, nil
    }
    return "", errNotFound
}

main.go

package main

import (
    "fmt"

    "github.com/learngo/mydict"
)

func main() {
    dictionary := mydict.Dictionary{"first": "First word"}
    definition, err := dictionary.Search("second")
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(definition)
    }
}


//결과
Not Found

Add 메서드 만들기

mydict.go

...
var errNotFound = errors.New("Not Found")
var errWordExists = errors.New("Word already Exists")

func (d Dictionary) Add(word, def string) error {
    _, err := d.Search(word)
    switch err {
    case errNotFound:
        d[word] = def
    case nil:
        return errWordExists
    }
    return nil
}

main.go

package main

import (
    "fmt"
    "github.com/learngo/mydict"
)

func main() {
    dictionary := mydict.Dictionary{"first": "First word"}
    word := "hello"
    definition := "Greeting"

    err := dictionary.Add(word, definition)
    if err != nil {
        fmt.Println(err)
    }

    hello, _ := dictionary.Search(word)
    fmt.Println("found", word, ", definition:", hello)

    err2 := dictionary.Add(word, definition)
    if err2 != nil {
        fmt.Println(err2)
    }

}

Update 메서드 만들기

mydict.go

...
var errNotFound = errors.New("Not Found")
var errWordExists = errors.New("Word already Exists")
var errCantUpdate = errors.New("Cant update non-existing word")

// Update a word
func (d Dictionary) Update(word, def string) error {
    _, err := d.Search(word)
    switch err {
    case nil:
        d[word] = def
    case errNotFound:
        return errCantUpdate
    }
    return nil
}

main.do

package main

import (
    "fmt"

    "github.com/learngo/mydict"
)

func main() {
    dictionary := mydict.Dictionary{"first": "First word"}
    word := "hello"

    dictionary.Add(word, "First")
    dictionary.Update(word, "Second")

    result, _ := dictionary.Search(word)
    fmt.Println(result)
}

//결과
Second

Delete 메서드 만들기

mydict.go

...
func (d Dictionary) Delete(word string) {
    delete(d, word)
}

main.go

package main

import (
    "fmt"

    "github.com/learngo/mydict"
)

func main() {
    dictionary := mydict.Dictionary{"first": "First word"}
    word := "hello"

    dictionary.Add(word, "First")
    dictionary.Update(word, "Second")
    result, _ := dictionary.Search(word)
    fmt.Println(result)

    dictionary.Delete(word)
    result, err := dictionary.Search(word)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(result)
    }
}

//결과
Second
Not Found



URL Checker 만들기

하나씩 순서대로 URL을 처리하는 기능을 만들었다.

main.go

package main

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

func main() {
    var results = map[string]string{}

    urls := []string{
        "https://www.google.com/",
        "https://www.naver.com/",
        "https://www.reddit.com/",
        "https://www.airbnb.com/",
    }

    for _, url := range urls {
        result := "OK"
        err := hitUrl(url)
        if err != nil {
            result = "FAILED"
        }
        results[url] = result
    }

    for url, result := range results {
        fmt.Println(url, result)
    }
}

var errRequestFailed = errors.New("Request Failed")

func hitUrl(url string) error {
    fmt.Println("Checing: ", url)
    resp, err := http.Get(url)
    if err != nil || resp.StatusCode >= 400 {
        fmt.Println(err, resp.StatusCode)
        return errRequestFailed
    }
    return nil

}

//결과
Checing:  https://www.google.com/
Checing:  https://www.naver.com/
Checing:  https://www.reddit.com/
Checing:  https://www.airbnb.com/
https://www.google.com/ OK
https://www.naver.com/ OK
https://www.reddit.com/ OK
https://www.airbnb.com/ OK

여기서 알면 좋은 부분은 아래부분을

var results = map[string]string{}

이렇게 써도 된다.

var results = make(map[string]string)

capacity 값이 고정이라면 이렇게도 가능하다.

results := make(map[string]string, 1000)

아래와 같은 케이스도 있다.

var results map[string]string

fmt.Println(results == nil) // true
results["a"] = "1"          // panic!

초기화를 하지 않고 선언을 할 수 있다. 선언만 하면 아무의미 없는 값으로 nil 이라는 값을 갖고 있지만 값을 추가하는 행동따윈 할 수 없다.



Goroutines

고루틴이란 기본적으로 다른 함수와 동시에 실행시키는 함수


아래와 같이 순차적으로 동작하는 Top-down 방식이 일반적인 프로그래밍 방식이다. 총 20초가 걸렸다.

main.go

package main

import (
    "fmt"
    "time"
)

func main() {
    sexyCount("yjy")
    sexyCount("kkk")
}

func sexyCount(person string) {
    for i := 0; i < 10; i++ {
        fmt.Println(person, "is sexy", i)
        time.Sleep(time.Second)
    }
}

//결과
yjy is sexy 0
yjy is sexy 1
yjy is sexy 2
yjy is sexy 3
yjy is sexy 4
yjy is sexy 5
yjy is sexy 6
yjy is sexy 7
yjy is sexy 8
yjy is sexy 9
kkk is sexy 0
kkk is sexy 1
kkk is sexy 2
kkk is sexy 3
kkk is sexy 4
kkk is sexy 5
kkk is sexy 6
kkk is sexy 7
kkk is sexy 8
kkk is sexy 9

고루틴을 적용하면 병행처리하여 10초에 처리할 수 있다.

앞에 go 키워드만 추가해주면 된다.

func main() {
    go sexyCount("yjy")
    sexyCount("kkk")
}
...

//결과
kkk is sexy 0
yjy is sexy 0
yjy is sexy 1
kkk is sexy 1
kkk is sexy 2
yjy is sexy 2
yjy is sexy 3
kkk is sexy 3
kkk is sexy 4
yjy is sexy 4
yjy is sexy 5
kkk is sexy 5
kkk is sexy 6
yjy is sexy 6
yjy is sexy 7
kkk is sexy 7
kkk is sexy 8
yjy is sexy 8
yjy is sexy 9
kkk is sexy 9

다만 주의할 점이 있다. 이렇게 go 키워드를 모두 추가하여 더이상 main 함수가 할일이 없어지게되면 main 함수 자체가 바로 종료되어서 아무 작업도 할 수 없게 된다. 이것의 고루틴의 약속이다.

func main() {
    go sexyCount("yjy")
    go sexyCount("kkk")
}
...

아래와 같은 방식은 고루틴이 yjy를 담당하고 kkk를 main이 작업을 담당했기 때문에 가능햇던것이다.

func main() {
    go sexyCount("yjy")
    sexyCount("kkk")
}

둘다 go 키워드를 적용해보고 main에게 따로 할일을 주면 동작할 것이다.

main에게 5초동안 기다리라는 작업을 주었고 정말 5초동안 고루틴에의해 병렬동시 실행이 된것을 확인할 수 있다.

func main() {
    go sexyCount("yjy")
    go sexyCount("kkk")
    time.Sleep(time.Second * 5)
}

//결과
kkk is sexy 0
yjy is sexy 0
yjy is sexy 1
kkk is sexy 1
kkk is sexy 2
yjy is sexy 2
yjy is sexy 3
kkk is sexy 3
kkk is sexy 4
yjy is sexy 4

func main(){
    go playYadong()
    go sex()
    time.Sleep(time.Second * 5)
}

Channels

channel은 고루틴과 메인함수 사이 혹은 고루틴끼리의 정보를 전달하기 위한 방법이다.


고루틴이 동작하기 위해서는 결국 main 함수가 어떤일을 하고 있어야만 실행이 가능한데 항상 sleep만 하고 있을수는 없는일이다.

때문에 channel을 이용하여 고루틴으로 부터 완료된 내용을 수신하는 역할을 main 함수가 하면 될것이다.

그래서 핵심은 main 함수에게 할일을 주어 고루틴의 작업이 멈추지 않도록 하는것.

main.go

package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan bool) // bool 타입의 결과를 받을 채널 선언

    people := [4]string{"yjy", "kkk", "jjj", "mmm"}
    for _, person := range people {
        go isSexy(person, c)
    }

    for i := 0; i < len(people); i++ {
        fmt.Println(<-c) // 채널로부터 값을 가져오는 오퍼레이션은 blocking 오퍼레이션이다,
    }
}

func isSexy(person string, c chan bool) {
    time.Sleep(time.Second * 5)
    c <- true // 채널에게 true값을 전달
}

//결과
waiting for  0
mmm is sexy
waiting for  1
jjj is sexy
waiting for  2
kkk is sexy
waiting for  3
yjy is sexy

고루틴의 Concurrency 때문에 people의 배열 순서와 결과값의 순서가 같지 않게됨을 확인 할 수 있다.

참고)

헷갈리지 말자 c<-"data" 데이터를 채널에 넣는다. <-c 데이터를 채널에서 가져온다.



고루틴,채널을 적용하여 URL Checker 다시 만들어보기

기존 main.go

package main

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

func main() {
    var results = map[string]string{}

    urls := []string{
        "https://www.google.com/",
        "https://www.naver.com/",
        "https://www.reddit.com/",
        "https://www.airbnb.com/",
    }

    for _, url := range urls {
        result := "OK"
        err := hitUrl(url)
        if err != nil {
            result = "FAILED"
        }
        results[url] = result
    }

    for url, result := range results {
        fmt.Println(url, result)
    }
}

var errRequestFailed = errors.New("Request Failed")

func hitUrl(url string) error {
    fmt.Println("Checing: ", url)
    resp, err := http.Get(url)
    if err != nil || resp.StatusCode >= 400 {
        fmt.Println(err, resp.StatusCode)
        return errRequestFailed
    }
    return nil
}

고루틴 적용후 main.go

package main

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

type result struct {
    url    string
    status string
}

var errRequestFailed = errors.New("Request Failed")

func main() {
    results := map[string]string{}
    c := make(chan result)

    urls := []string{
        "https://www.google.com/",
        "https://www.naver.com/",
        "https://www.reddit.com/",
        "https://www.airbnb.com/",
    }

    for _, url := range urls {
        go hitUrl(url, c)
    }

    for i := 0; i < len(urls); i++ {
        result := <-c
        results[result.url] = result.status
    }

    for url, status := range results {
        fmt.Println(url, status)
    }
}

// 해당 채널은 받을 수는 없고 보내기만 가능(c chan<- result)
func hitUrl(url string, c chan<- result) {
    fmt.Println("Checing: ", url)
    // fmt.Println(<-c), 에러발생!, 채널을 c chan<- result 이런식으로 받을수만 있게 선언했기 때문에 채널에서 꺼내올수는 없다.

    resp, err := http.Get(url)
    status := "OK"
    if err != nil || resp.StatusCode >= 400 {
        status = "FAILED"
    }

    c <- result{url: url, status: status}
}



BY 윤주영.







출처:

https://nomadcoders.co/go-for-beginners/

728x90
반응형
LIST